summaryrefslogtreecommitdiffstats
path: root/documentapi/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 /documentapi/src/tests
Publish
Diffstat (limited to 'documentapi/src/tests')
-rw-r--r--documentapi/src/tests/.gitignore3
-rwxr-xr-xdocumentapi/src/tests/create-test.sh75
-rw-r--r--documentapi/src/tests/loadtypes/.gitignore3
-rw-r--r--documentapi/src/tests/loadtypes/CMakeLists.txt10
-rw-r--r--documentapi/src/tests/loadtypes/loadtypetest.cpp80
-rw-r--r--documentapi/src/tests/loadtypes/testrunner.cpp13
-rw-r--r--documentapi/src/tests/messagebus/.gitignore5
-rw-r--r--documentapi/src/tests/messagebus/CMakeLists.txt8
-rw-r--r--documentapi/src/tests/messagebus/documentrouteselectorpolicy.cfg4
-rw-r--r--documentapi/src/tests/messagebus/messagebus_test.cpp104
-rw-r--r--documentapi/src/tests/messages/.gitignore5
-rw-r--r--documentapi/src/tests/messages/CMakeLists.txt38
-rw-r--r--documentapi/src/tests/messages/error_codes_test.cpp124
-rw-r--r--documentapi/src/tests/messages/messages50app.cpp8
-rw-r--r--documentapi/src/tests/messages/messages50test.cpp1225
-rw-r--r--documentapi/src/tests/messages/messages50test.h56
-rw-r--r--documentapi/src/tests/messages/messages51app.cpp8
-rw-r--r--documentapi/src/tests/messages/messages51test.cpp111
-rw-r--r--documentapi/src/tests/messages/messages51test.h18
-rw-r--r--documentapi/src/tests/messages/messages52app.cpp8
-rw-r--r--documentapi/src/tests/messages/messages52test.cpp122
-rw-r--r--documentapi/src/tests/messages/messages52test.h23
-rw-r--r--documentapi/src/tests/messages/testbase.cpp197
-rw-r--r--documentapi/src/tests/messages/testbase.h61
-rw-r--r--documentapi/src/tests/policies/.gitignore5
-rw-r--r--documentapi/src/tests/policies/CMakeLists.txt9
-rw-r--r--documentapi/src/tests/policies/policies_test.cpp1252
-rw-r--r--documentapi/src/tests/policies/testframe.cpp336
-rw-r--r--documentapi/src/tests/policies/testframe.h219
-rw-r--r--documentapi/src/tests/policyfactory/.gitignore4
-rw-r--r--documentapi/src/tests/policyfactory/CMakeLists.txt8
-rw-r--r--documentapi/src/tests/policyfactory/DESC1
-rw-r--r--documentapi/src/tests/policyfactory/FILES1
-rw-r--r--documentapi/src/tests/policyfactory/policyfactory.cpp115
-rw-r--r--documentapi/src/tests/priority/.gitignore1
-rw-r--r--documentapi/src/tests/priority/CMakeLists.txt8
-rw-r--r--documentapi/src/tests/priority/DESC1
-rw-r--r--documentapi/src/tests/priority/FILES1
-rw-r--r--documentapi/src/tests/priority/priority.cpp59
-rw-r--r--documentapi/src/tests/replymerger/.gitignore1
-rw-r--r--documentapi/src/tests/replymerger/CMakeLists.txt8
-rw-r--r--documentapi/src/tests/replymerger/DESC1
-rw-r--r--documentapi/src/tests/replymerger/FILES1
-rw-r--r--documentapi/src/tests/replymerger/replymerger_test.cpp304
-rw-r--r--documentapi/src/tests/routablefactory/.gitignore4
-rw-r--r--documentapi/src/tests/routablefactory/CMakeLists.txt8
-rw-r--r--documentapi/src/tests/routablefactory/DESC1
-rw-r--r--documentapi/src/tests/routablefactory/FILES1
-rw-r--r--documentapi/src/tests/routablefactory/routablefactory.cpp242
-rw-r--r--documentapi/src/tests/systemstate/.gitignore4
-rw-r--r--documentapi/src/tests/systemstate/CMakeLists.txt8
-rw-r--r--documentapi/src/tests/systemstate/DESC3
-rw-r--r--documentapi/src/tests/systemstate/FILES1
-rw-r--r--documentapi/src/tests/systemstate/systemstate.cpp225
54 files changed, 5141 insertions, 0 deletions
diff --git a/documentapi/src/tests/.gitignore b/documentapi/src/tests/.gitignore
new file mode 100644
index 00000000000..a3e9c375723
--- /dev/null
+++ b/documentapi/src/tests/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+*_test
diff --git a/documentapi/src/tests/create-test.sh b/documentapi/src/tests/create-test.sh
new file mode 100755
index 00000000000..5debc5f635a
--- /dev/null
+++ b/documentapi/src/tests/create-test.sh
@@ -0,0 +1,75 @@
+#!/bin/sh
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+gen_ignore_file() {
+ echo "generating '$1' ..."
+ echo ".depend" > $1
+ echo "Makefile" >> $1
+ echo "${test}_test" >> $1
+}
+
+gen_project_file() {
+ echo "generating '$1' ..."
+ echo "APPLICATION ${test}_test" > $1
+ echo "OBJS $test" >> $1
+ echo "LIBS documentapi/documentapi" >> $1
+ echo "EXTERNALLIBS vespalib vespalog document config messagebus-test" >> $1
+ echo "EXTERNALLIBS messagebus config slobrokserver vespalib" >> $1
+ echo "" >> $1
+ echo "CUSTOMMAKE" >> $1
+ echo "test: depend ${test}_test" >> $1
+ echo -e "\t@./${test}_test" >> $1
+}
+
+gen_source() {
+ echo "generating '$1' ..."
+ echo "#include <vespa/log/log.h>" > $1
+ echo "LOG_SETUP(\"${test}_test\");" >> $1
+ echo "" >> $1
+ echo "#include <vespa/fastos/fastos.h>" >> $1
+ echo "#include <vespa/vespalib/testkit/testapp.h>" >> $1
+ echo "" >> $1
+ echo "//using namespace documentapi;" >> $1
+ echo "" >> $1
+ echo "TEST_SETUP(Test);" >> $1
+ echo "" >> $1
+ echo "int" >> $1
+ echo "Test::Main()" >> $1
+ echo "{" >> $1
+ echo " TEST_INIT(\"${test}_test\");" >> $1
+ echo " TEST_DONE();" >> $1
+ echo "}" >> $1
+}
+
+gen_desc() {
+ echo "generating '$1' ..."
+ echo "$test test. Take a look at $test.cpp for details." > $1
+}
+
+gen_file_list() {
+ echo "generating '$1' ..."
+ echo "$test.cpp" > $1
+}
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <name>"
+ echo " name: name of the test to create"
+ exit 1
+fi
+
+test=$1
+if [ -e $test ]; then
+ echo "$test already present, don't want to mess it up..."
+ exit 1
+fi
+
+echo "creating directory '$test' ..."
+mkdir -p $test || exit 1
+cd $test || exit 1
+test=`basename $test`
+
+gen_ignore_file .cvsignore
+gen_project_file fastos.project
+gen_source $test.cpp
+gen_desc DESC
+gen_file_list FILES
diff --git a/documentapi/src/tests/loadtypes/.gitignore b/documentapi/src/tests/loadtypes/.gitignore
new file mode 100644
index 00000000000..497fe4d4b3f
--- /dev/null
+++ b/documentapi/src/tests/loadtypes/.gitignore
@@ -0,0 +1,3 @@
+.depend
+Makefile
+documentapi_loadtype_test_app
diff --git a/documentapi/src/tests/loadtypes/CMakeLists.txt b/documentapi/src/tests/loadtypes/CMakeLists.txt
new file mode 100644
index 00000000000..7c1e92b087f
--- /dev/null
+++ b/documentapi/src/tests/loadtypes/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(documentapi_loadtype_test_app
+ SOURCES
+ loadtypetest.cpp
+ testrunner.cpp
+ DEPENDS
+ documentapi
+ vdstestlib
+)
+vespa_add_test(NAME documentapi_loadtype_test_app COMMAND documentapi_loadtype_test_app)
diff --git a/documentapi/src/tests/loadtypes/loadtypetest.cpp b/documentapi/src/tests/loadtypes/loadtypetest.cpp
new file mode 100644
index 00000000000..42b58b1a9c9
--- /dev/null
+++ b/documentapi/src/tests/loadtypes/loadtypetest.cpp
@@ -0,0 +1,80 @@
+// 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 <vespa/vdstestlib/cppunit/macros.h>
+#include <vespa/documentapi/loadtypes/loadtypeset.h>
+
+namespace documentapi {
+
+struct LoadTypeTest : public CppUnit::TestFixture {
+
+ void testConfig();
+
+ CPPUNIT_TEST_SUITE(LoadTypeTest);
+ CPPUNIT_TEST(testConfig);
+ CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(LoadTypeTest);
+
+#define ASSERT_CONFIG_FAILURE(configId, error) \
+ try { \
+ LoadTypeSet createdFromConfigId(configId); \
+ CPPUNIT_FAIL("Config was expected to fail with error: " \
+ + string(error)); \
+ } catch (config::InvalidConfigException& e) { \
+ CPPUNIT_ASSERT_CONTAIN(string(error), e.getMessage()); \
+ }
+
+void
+LoadTypeTest::testConfig()
+{
+ // Using id 0 is illegal. Reserved for default type.
+ ASSERT_CONFIG_FAILURE(
+ "raw:"
+ "type[1]\n"
+ "type[0].id 0\n"
+ "type[0].name \"foo\"\n"
+ "type[0].priority \"\"",
+ "Load type identifiers need to be");
+ // Using name "default" is illegal. Reserved for default type.
+ ASSERT_CONFIG_FAILURE(
+ "raw:"
+ "type[1]\n"
+ "type[0].id 1\n"
+ "type[0].name \"default\"\n"
+ "type[0].priority \"\"", "Load type names need to be");
+ // Identifiers need to be unique.
+ ASSERT_CONFIG_FAILURE(
+ "raw:"
+ "type[2]\n"
+ "type[0].id 1\n"
+ "type[0].name \"test\"\n"
+ "type[0].priority \"\"\n"
+ "type[1].id 1\n"
+ "type[1].name \"testa\"\n"
+ "type[1].priority \"\"", "Load type identifiers need to be");
+ // Names need to be unique.
+ ASSERT_CONFIG_FAILURE(
+ "raw:"
+ "type[2]\n"
+ "type[0].id 1\n"
+ "type[0].name \"test\"\n"
+ "type[0].priority \"\"\n"
+ "type[1].id 2\n"
+ "type[1].name \"test\"\n"
+ "type[1].priority \"\"" , "Load type names need to be");
+ LoadTypeSet set("raw:"
+ "type[3]\n"
+ "type[0].id 1\n"
+ "type[0].name \"user\"\n"
+ "type[0].priority \"\"\n"
+ "type[1].id 2\n"
+ "type[1].name \"maintenance\"\n"
+ "type[1].priority \"\"\n"
+ "type[2].id 3\n"
+ "type[2].name \"put\"\n"
+ "type[2].priority \"\""
+ );
+}
+
+} // documentapi
diff --git a/documentapi/src/tests/loadtypes/testrunner.cpp b/documentapi/src/tests/loadtypes/testrunner.cpp
new file mode 100644
index 00000000000..71200f84224
--- /dev/null
+++ b/documentapi/src/tests/loadtypes/testrunner.cpp
@@ -0,0 +1,13 @@
+// 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 <vespa/log/log.h>
+#include <vespa/vdstestlib/cppunit/cppunittestrunner.h>
+
+LOG_SETUP("storagecppunittests");
+
+int
+main(int argc, char **argv)
+{
+ vdstestlib::CppUnitTestRunner testRunner;
+ return testRunner.run(argc, argv);
+}
diff --git a/documentapi/src/tests/messagebus/.gitignore b/documentapi/src/tests/messagebus/.gitignore
new file mode 100644
index 00000000000..e409c623d1b
--- /dev/null
+++ b/documentapi/src/tests/messagebus/.gitignore
@@ -0,0 +1,5 @@
+*_test
+.depend
+Makefile
+log
+documentapi_messagebus_test_app
diff --git a/documentapi/src/tests/messagebus/CMakeLists.txt b/documentapi/src/tests/messagebus/CMakeLists.txt
new file mode 100644
index 00000000000..0f0760c1b57
--- /dev/null
+++ b/documentapi/src/tests/messagebus/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(documentapi_messagebus_test_app
+ SOURCES
+ messagebus_test.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_messagebus_test_app COMMAND documentapi_messagebus_test_app)
diff --git a/documentapi/src/tests/messagebus/documentrouteselectorpolicy.cfg b/documentapi/src/tests/messagebus/documentrouteselectorpolicy.cfg
new file mode 100644
index 00000000000..9f56aa29020
--- /dev/null
+++ b/documentapi/src/tests/messagebus/documentrouteselectorpolicy.cfg
@@ -0,0 +1,4 @@
+route[1]
+route[0].name foo
+route[0].selector testdoc
+route[0].feed bigmac
diff --git a/documentapi/src/tests/messagebus/messagebus_test.cpp b/documentapi/src/tests/messagebus/messagebus_test.cpp
new file mode 100644
index 00000000000..b55d35825fe
--- /dev/null
+++ b/documentapi/src/tests/messagebus/messagebus_test.cpp
@@ -0,0 +1,104 @@
+// 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 <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/documentapi/documentapi.h>
+#include <vespa/log/log.h>
+#include <vespa/messagebus/message.h>
+#include <vespa/messagebus/routable.h>
+#include <vespa/vdslib/state/clusterstate.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+LOG_SETUP("messages_test");
+
+using document::DocumentTypeRepo;
+using document::readDocumenttypesConfig;
+using namespace documentapi;
+using mbus::Blob;
+using mbus::Routable;
+using mbus::IRoutingPolicy;
+
+class Test : public vespalib::TestApp {
+ DocumentTypeRepo::SP _repo;
+
+public:
+ int Main();
+
+private:
+ void testMessage();
+ void testProtocol();
+};
+
+TEST_APPHOOK(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT(_argv[0]);
+ _repo.reset(new DocumentTypeRepo(readDocumenttypesConfig("../../../test/cfg/testdoctypes.cfg")));
+
+ testMessage(); TEST_FLUSH();
+ testProtocol(); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+void Test::testMessage() {
+ const document::DataType *testdoc_type = _repo->getDocumentType("testdoc");
+
+ // Test one update.
+ UpdateDocumentMessage upd1(
+ document::DocumentUpdate::SP(
+ new document::DocumentUpdate(*testdoc_type,
+ document::DocumentId(document::DocIdString(
+ "testdoc", "testme1")))));
+
+ EXPECT_TRUE(upd1.getType() == DocumentProtocol::MESSAGE_UPDATEDOCUMENT);
+ EXPECT_TRUE(upd1.getProtocol() == "document");
+
+ LoadTypeSet set;
+ DocumentProtocol protocol(set, _repo);
+
+ Blob blob = protocol.encode(vespalib::Version(5,0), upd1);
+ EXPECT_TRUE(blob.size() > 0);
+
+ Routable::UP dec1 = protocol.decode(vespalib::Version(5,0), blob);
+ EXPECT_TRUE(dec1.get() != NULL);
+ EXPECT_TRUE(dec1->isReply() == false);
+ EXPECT_TRUE(dec1->getType() == DocumentProtocol::MESSAGE_UPDATEDOCUMENT);
+
+ // Compare to another.
+ UpdateDocumentMessage upd2(
+ document::DocumentUpdate::SP(
+ new document::DocumentUpdate(*testdoc_type,
+ document::DocumentId(document::DocIdString(
+ "testdoc", "testme2")))));
+ EXPECT_TRUE(!(upd1.getDocumentUpdate()->getId() == upd2.getDocumentUpdate()->getId()));
+
+ DocumentMessage& msg2 = static_cast<DocumentMessage&>(upd2);
+ EXPECT_TRUE(msg2.getType() == DocumentProtocol::MESSAGE_UPDATEDOCUMENT);
+}
+
+void Test::testProtocol() {
+ LoadTypeSet set;
+ DocumentProtocol protocol(set, _repo);
+ EXPECT_TRUE(protocol.getName() == "document");
+
+ IRoutingPolicy::UP policy = protocol.createPolicy(string("SearchRow"),string(""));
+ EXPECT_TRUE(policy.get() != NULL);
+
+ policy = protocol.createPolicy(string("SearchColumn"),string(""));
+ EXPECT_TRUE(policy.get() != NULL);
+
+ policy = protocol.createPolicy(string("DocumentRouteSelector"), string("file:documentrouteselectorpolicy.cfg"));
+ EXPECT_TRUE(policy.get() != NULL);
+
+ policy = protocol.createPolicy(string(""),string(""));
+ EXPECT_TRUE(policy.get() == NULL);
+
+ policy = protocol.createPolicy(string("Balle"),string(""));
+ EXPECT_TRUE(policy.get() == NULL);
+}
+
+
diff --git a/documentapi/src/tests/messages/.gitignore b/documentapi/src/tests/messages/.gitignore
new file mode 100644
index 00000000000..298c9cd21c6
--- /dev/null
+++ b/documentapi/src/tests/messages/.gitignore
@@ -0,0 +1,5 @@
+*_test
+*_app
+.depend
+Makefile
+log
diff --git a/documentapi/src/tests/messages/CMakeLists.txt b/documentapi/src/tests/messages/CMakeLists.txt
new file mode 100644
index 00000000000..bfffd0d0502
--- /dev/null
+++ b/documentapi/src/tests/messages/CMakeLists.txt
@@ -0,0 +1,38 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(documentapi_messages50_test_app
+ SOURCES
+ testbase.cpp
+ messages50test.cpp
+ messages50app.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_messages50_test_app COMMAND documentapi_messages50_test_app)
+vespa_add_executable(documentapi_messages51_test_app
+ SOURCES
+ testbase.cpp
+ messages50test.cpp
+ messages51test.cpp
+ messages51app.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_messages51_test_app COMMAND documentapi_messages51_test_app)
+vespa_add_executable(documentapi_messages52_test_app
+ SOURCES
+ testbase.cpp
+ messages50test.cpp
+ messages51test.cpp
+ messages52test.cpp
+ messages52app.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_messages52_test_app COMMAND documentapi_messages52_test_app)
+vespa_add_executable(documentapi_error_codes_test_app_app
+ SOURCES
+ error_codes_test.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_error_codes_test_app_app COMMAND documentapi_error_codes_test_app_app)
diff --git a/documentapi/src/tests/messages/error_codes_test.cpp b/documentapi/src/tests/messages/error_codes_test.cpp
new file mode 100644
index 00000000000..1714fb70a04
--- /dev/null
+++ b/documentapi/src/tests/messages/error_codes_test.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.
+// Copyright 2015 Yahoo Technologies Norway AS
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/documentapi/messagebus/documentprotocol.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <string>
+#include <exception>
+#include <map>
+
+using NamedErrorCodes = std::map<std::string, uint32_t>;
+
+// DocumentAPI C++ module uses Ye Olde Test Framework.
+class ErrorCodesTest : public vespalib::TestApp {
+ int Main() override;
+ void error_codes_match_java_definitions();
+ NamedErrorCodes all_document_protocol_error_codes();
+};
+
+TEST_APPHOOK(ErrorCodesTest);
+
+// ERROR_CODE_KV(FOO) -> {"FOO", DocumentProtocol::FOO}
+#define ERROR_CODE_KV(code_name) \
+ {#code_name, DocumentProtocol::code_name}
+
+NamedErrorCodes
+ErrorCodesTest::all_document_protocol_error_codes()
+{
+ using documentapi::DocumentProtocol;
+ return {
+ ERROR_CODE_KV(ERROR_MESSAGE_IGNORED),
+ ERROR_CODE_KV(ERROR_POLICY_FAILURE),
+ ERROR_CODE_KV(ERROR_DOCUMENT_NOT_FOUND),
+ // Error code not consistently named between languages!
+ // Java: ERROR_DOCUMENT_EXISTS, C++: ERROR_EXISTS
+ // Names must be consistent in test or checking will fail.
+ {"ERROR_DOCUMENT_EXISTS", DocumentProtocol::ERROR_EXISTS},
+ ERROR_CODE_KV(ERROR_REJECTED),
+ ERROR_CODE_KV(ERROR_NOT_IMPLEMENTED),
+ ERROR_CODE_KV(ERROR_ILLEGAL_PARAMETERS),
+ ERROR_CODE_KV(ERROR_UNKNOWN_COMMAND),
+ ERROR_CODE_KV(ERROR_NO_SPACE),
+ ERROR_CODE_KV(ERROR_IGNORED),
+ ERROR_CODE_KV(ERROR_INTERNAL_FAILURE),
+ ERROR_CODE_KV(ERROR_TEST_AND_SET_CONDITION_FAILED),
+ ERROR_CODE_KV(ERROR_PROCESSING_FAILURE),
+ ERROR_CODE_KV(ERROR_TIMESTAMP_EXIST),
+ ERROR_CODE_KV(ERROR_NODE_NOT_READY),
+ ERROR_CODE_KV(ERROR_WRONG_DISTRIBUTION),
+ ERROR_CODE_KV(ERROR_ABORTED),
+ ERROR_CODE_KV(ERROR_BUSY),
+ ERROR_CODE_KV(ERROR_NOT_CONNECTED),
+ ERROR_CODE_KV(ERROR_DISK_FAILURE),
+ ERROR_CODE_KV(ERROR_IO_FAILURE),
+ ERROR_CODE_KV(ERROR_BUCKET_NOT_FOUND),
+ ERROR_CODE_KV(ERROR_BUCKET_DELETED),
+ ERROR_CODE_KV(ERROR_STALE_TIMESTAMP),
+ ERROR_CODE_KV(ERROR_SUSPENDED)
+ };
+}
+
+#undef ERROR_CODE_KV
+
+namespace {
+
+std::string read_file(const std::string& file_name) {
+ std::ifstream ifs(file_name);
+ if (!ifs.is_open()) {
+ throw std::runtime_error("file '" + file_name + "' does not exist");
+ }
+ std::ostringstream oss;
+ oss << ifs.rdbuf();
+ return oss.str();
+}
+
+void write_file(const std::string& file_name,
+ const std::string& content)
+{
+ std::ofstream ofs(file_name, std::ios_base::trunc);
+ ofs << content;
+}
+
+std::string to_sorted_key_value_string(const NamedErrorCodes& codes) {
+ std::ostringstream os;
+ bool emit_newline = false;
+ for (auto& kv : codes) {
+ if (emit_newline) {
+ os << '\n';
+ }
+ os << kv.first << ' ' << kv.second;
+ emit_newline = true;
+ }
+ return os.str();
+}
+
+std::string path_prefixed(const std::string& file_name) {
+ return "../../../test/crosslanguagefiles/" + file_name;
+}
+
+} // anon ns
+
+void
+ErrorCodesTest::error_codes_match_java_definitions()
+{
+ NamedErrorCodes codes(all_document_protocol_error_codes());
+ auto cpp_golden_file = path_prefixed("HEAD-cpp-golden-error-codes.txt");
+ auto cpp_golden_data = to_sorted_key_value_string(codes);
+ write_file(cpp_golden_file, cpp_golden_data);
+
+ auto java_golden_file = path_prefixed("HEAD-java-golden-error-codes.txt");
+ auto java_golden_data = read_file(java_golden_file);
+ EXPECT_EQUAL(cpp_golden_data, java_golden_data);
+}
+
+int
+ErrorCodesTest::Main()
+{
+ TEST_INIT("error_codes_test");
+ error_codes_match_java_definitions();
+ TEST_FLUSH();
+ TEST_DONE();
+}
+
diff --git a/documentapi/src/tests/messages/messages50app.cpp b/documentapi/src/tests/messages/messages50app.cpp
new file mode 100644
index 00000000000..64532d4fd14
--- /dev/null
+++ b/documentapi/src/tests/messages/messages50app.cpp
@@ -0,0 +1,8 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP("messages50");
+
+#include "messages50test.h"
+
+TEST_APPHOOK(Messages50Test);
diff --git a/documentapi/src/tests/messages/messages50test.cpp b/documentapi/src/tests/messages/messages50test.cpp
new file mode 100644
index 00000000000..1e0069d50b9
--- /dev/null
+++ b/documentapi/src/tests/messages/messages50test.cpp
@@ -0,0 +1,1225 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP(".test");
+
+#include "messages50test.h"
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/document/update/fieldpathupdates.h>
+#include <vespa/documentapi/documentapi.h>
+#include <vespa/vdslib/container/writabledocumentlist.h>
+
+using document::DataType;
+using document::DocumentTypeRepo;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Setup
+//
+///////////////////////////////////////////////////////////////////////////////
+
+Messages50Test::Messages50Test()
+{
+ // This list MUST mirror the list of routable factories from the DocumentProtocol constructor that support
+ // version 5.0. When adding tests to this list, please KEEP THEM ORDERED alphabetically like they are now.
+ putTest(DocumentProtocol::MESSAGE_BATCHDOCUMENTUPDATE, TEST_METHOD(Messages50Test::testBatchDocumentUpdateMessage));
+ putTest(DocumentProtocol::MESSAGE_CREATEVISITOR, TEST_METHOD(Messages50Test::testCreateVisitorMessage));
+ putTest(DocumentProtocol::MESSAGE_DESTROYVISITOR, TEST_METHOD(Messages50Test::testDestroyVisitorMessage));
+ putTest(DocumentProtocol::MESSAGE_DOCUMENTLIST, TEST_METHOD(Messages50Test::testDocumentListMessage));
+ putTest(DocumentProtocol::MESSAGE_DOCUMENTSUMMARY, TEST_METHOD(Messages50Test::testDocumentSummaryMessage));
+ putTest(DocumentProtocol::MESSAGE_EMPTYBUCKETS, TEST_METHOD(Messages50Test::testEmptyBucketsMessage));
+ putTest(DocumentProtocol::MESSAGE_GETBUCKETLIST, TEST_METHOD(Messages50Test::testGetBucketListMessage));
+ putTest(DocumentProtocol::MESSAGE_GETBUCKETSTATE, TEST_METHOD(Messages50Test::testGetBucketStateMessage));
+ putTest(DocumentProtocol::MESSAGE_GETDOCUMENT, TEST_METHOD(Messages50Test::testGetDocumentMessage));
+ putTest(DocumentProtocol::MESSAGE_MAPVISITOR, TEST_METHOD(Messages50Test::testMapVisitorMessage));
+ putTest(DocumentProtocol::MESSAGE_MULTIOPERATION, TEST_METHOD(Messages50Test::testMultiOperationMessage));
+ putTest(DocumentProtocol::MESSAGE_PUTDOCUMENT, TEST_METHOD(Messages50Test::testPutDocumentMessage));
+ putTest(DocumentProtocol::MESSAGE_QUERYRESULT, TEST_METHOD(Messages50Test::testQueryResultMessage));
+ putTest(DocumentProtocol::MESSAGE_REMOVEDOCUMENT, TEST_METHOD(Messages50Test::testRemoveDocumentMessage));
+ putTest(DocumentProtocol::MESSAGE_REMOVELOCATION, TEST_METHOD(Messages50Test::testRemoveLocationMessage));
+ putTest(DocumentProtocol::MESSAGE_SEARCHRESULT, TEST_METHOD(Messages50Test::testSearchResultMessage));
+ putTest(DocumentProtocol::MESSAGE_STATBUCKET, TEST_METHOD(Messages50Test::testStatBucketMessage));
+ putTest(DocumentProtocol::MESSAGE_UPDATEDOCUMENT, TEST_METHOD(Messages50Test::testUpdateDocumentMessage));
+ putTest(DocumentProtocol::MESSAGE_VISITORINFO, TEST_METHOD(Messages50Test::testVisitorInfoMessage));
+
+ putTest(DocumentProtocol::REPLY_BATCHDOCUMENTUPDATE, TEST_METHOD(Messages50Test::testBatchDocumentUpdateReply));
+ putTest(DocumentProtocol::REPLY_CREATEVISITOR, TEST_METHOD(Messages50Test::testCreateVisitorReply));
+ putTest(DocumentProtocol::REPLY_DESTROYVISITOR, TEST_METHOD(Messages50Test::testDestroyVisitorReply));
+ putTest(DocumentProtocol::REPLY_DOCUMENTLIST, TEST_METHOD(Messages50Test::testDocumentListReply));
+ putTest(DocumentProtocol::REPLY_DOCUMENTSUMMARY, TEST_METHOD(Messages50Test::testDocumentSummaryReply));
+ putTest(DocumentProtocol::REPLY_EMPTYBUCKETS, TEST_METHOD(Messages50Test::testEmptyBucketsReply));
+ putTest(DocumentProtocol::REPLY_GETBUCKETLIST, TEST_METHOD(Messages50Test::testGetBucketListReply));
+ putTest(DocumentProtocol::REPLY_GETBUCKETSTATE, TEST_METHOD(Messages50Test::testGetBucketStateReply));
+ putTest(DocumentProtocol::REPLY_GETDOCUMENT, TEST_METHOD(Messages50Test::testGetDocumentReply));
+ putTest(DocumentProtocol::REPLY_MAPVISITOR, TEST_METHOD(Messages50Test::testMapVisitorReply));
+ putTest(DocumentProtocol::REPLY_MULTIOPERATION, TEST_METHOD(Messages50Test::testMultiOperationReply));
+ putTest(DocumentProtocol::REPLY_PUTDOCUMENT, TEST_METHOD(Messages50Test::testPutDocumentReply));
+ putTest(DocumentProtocol::REPLY_QUERYRESULT, TEST_METHOD(Messages50Test::testQueryResultReply));
+ putTest(DocumentProtocol::REPLY_REMOVEDOCUMENT, TEST_METHOD(Messages50Test::testRemoveDocumentReply));
+ putTest(DocumentProtocol::REPLY_REMOVELOCATION, TEST_METHOD(Messages50Test::testRemoveLocationReply));
+ putTest(DocumentProtocol::REPLY_SEARCHRESULT, TEST_METHOD(Messages50Test::testSearchResultReply));
+ putTest(DocumentProtocol::REPLY_STATBUCKET, TEST_METHOD(Messages50Test::testStatBucketReply));
+ putTest(DocumentProtocol::REPLY_UPDATEDOCUMENT, TEST_METHOD(Messages50Test::testUpdateDocumentReply));
+ putTest(DocumentProtocol::REPLY_VISITORINFO, TEST_METHOD(Messages50Test::testVisitorInfoReply));
+ putTest(DocumentProtocol::REPLY_WRONGDISTRIBUTION, TEST_METHOD(Messages50Test::testWrongDistributionReply));
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Tests
+//
+///////////////////////////////////////////////////////////////////////////////
+
+static const int MESSAGE_BASE_LENGTH = 5;
+
+namespace {
+
+document::Document::SP
+createDoc(const DocumentTypeRepo &repo, const string &type_name, const string &id)
+{
+ return document::Document::SP(new document::Document(
+ *repo.getDocumentType(type_name),
+ document::DocumentId(id)));
+}
+
+} // namespace
+
+bool
+Messages50Test::testGetBucketListMessage()
+{
+ GetBucketListMessage msg(document::BucketId(16, 123));
+ msg.setLoadType(_loadTypes["foo"]);
+ EXPECT_EQUAL(string("foo"), msg.getLoadType().getName());
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 12u, serialize("GetBucketListMessage", msg));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("GetBucketListMessage", DocumentProtocol::MESSAGE_GETBUCKETLIST, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ GetBucketListMessage &ref = static_cast<GetBucketListMessage&>(*obj);
+ EXPECT_EQUAL(string("foo"), ref.getLoadType().getName());
+ EXPECT_EQUAL(document::BucketId(16, 123), ref.getBucketId());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testEmptyBucketsMessage()
+{
+ std::vector<document::BucketId> bids;
+ for (size_t i=0; i < 13; ++i) {
+ bids.push_back(document::BucketId(16, i));
+ }
+
+ EmptyBucketsMessage msg(bids);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 112u, serialize("EmptyBucketsMessage", msg));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("EmptyBucketsMessage", DocumentProtocol::MESSAGE_EMPTYBUCKETS, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ EmptyBucketsMessage &ref = static_cast<EmptyBucketsMessage&>(*obj);
+ for (size_t i=0; i < 13; ++i) {
+ EXPECT_EQUAL(document::BucketId(16, i), ref.getBucketIds()[i]);
+ }
+ }
+ }
+ return true;
+}
+
+
+bool
+Messages50Test::testStatBucketMessage()
+{
+ StatBucketMessage msg(document::BucketId(16, 123), "id.user=123");
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 27u, serialize("StatBucketMessage", msg));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("StatBucketMessage", DocumentProtocol::MESSAGE_STATBUCKET, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ StatBucketMessage &ref = static_cast<StatBucketMessage&>(*obj);
+ EXPECT_EQUAL(document::BucketId(16, 123), ref.getBucketId());
+ EXPECT_EQUAL("id.user=123", ref.getDocumentSelection());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testCreateVisitorMessage() {
+ CreateVisitorMessage tmp("SomeLibrary", "myvisitor", "newyork", "london");
+ tmp.setDocumentSelection("true and false or true");
+ tmp.getParameters().set("myvar", "somevalue");
+ tmp.getParameters().set("anothervar", uint64_t(34));
+ tmp.getBuckets().push_back(document::BucketId(16, 1234));
+ tmp.setVisitRemoves(true);
+ tmp.setVisitorOrdering(document::OrderingSpecification::DESCENDING);
+ tmp.setMaxBucketsPerVisitor(2);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + (size_t)168, serialize("CreateVisitorMessage", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("CreateVisitorMessage", DocumentProtocol::MESSAGE_CREATEVISITOR, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ CreateVisitorMessage &ref = static_cast<CreateVisitorMessage&>(*obj);
+
+ EXPECT_EQUAL(string("SomeLibrary"), ref.getLibraryName());
+ EXPECT_EQUAL(string("myvisitor"), ref.getInstanceId());
+ EXPECT_EQUAL(string("newyork"), ref.getControlDestination());
+ EXPECT_EQUAL(string("london"), ref.getDataDestination());
+ EXPECT_EQUAL(string("true and false or true"), ref.getDocumentSelection());
+ EXPECT_EQUAL(uint32_t(8), ref.getMaximumPendingReplyCount());
+ EXPECT_EQUAL(true, ref.visitRemoves());
+ EXPECT_EQUAL(false, ref.visitHeadersOnly());
+ EXPECT_EQUAL(false, ref.visitInconsistentBuckets());
+ EXPECT_EQUAL(size_t(1), ref.getBuckets().size());
+ EXPECT_EQUAL(document::BucketId(16, 1234), ref.getBuckets()[0]);
+ EXPECT_EQUAL(string("somevalue"), ref.getParameters().get("myvar"));
+ EXPECT_EQUAL(uint64_t(34), ref.getParameters().get("anothervar", uint64_t(1)));
+ EXPECT_EQUAL(document::OrderingSpecification::DESCENDING, ref.getVisitorOrdering());
+ EXPECT_EQUAL(uint32_t(2), ref.getMaxBucketsPerVisitor());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testDestroyVisitorMessage()
+{
+ DestroyVisitorMessage tmp("myvisitor");
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + (size_t)17, serialize("DestroyVisitorMessage", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("DestroyVisitorMessage", DocumentProtocol::MESSAGE_DESTROYVISITOR, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ DestroyVisitorMessage &ref = static_cast<DestroyVisitorMessage&>(*obj);
+ EXPECT_EQUAL(string("myvisitor"), ref.getInstanceId());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testDocumentListMessage()
+{
+ document::Document::SP doc =
+ createDoc(getTypeRepo(), "testdoc", "userdoc:scheme:1234:");
+ DocumentListMessage::Entry entry(1234, doc, false);
+
+ DocumentListMessage tmp(document::BucketId(16, 1234));
+ tmp.getDocuments().push_back(entry);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + (size_t)63, serialize("DocumentListMessage", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("DocumentListMessage", DocumentProtocol::MESSAGE_DOCUMENTLIST, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ DocumentListMessage &ref = static_cast<DocumentListMessage&>(*obj);
+
+ EXPECT_EQUAL("userdoc:scheme:1234:", ref.getDocuments()[0].getDocument()->getId().toString());
+ EXPECT_EQUAL(1234, ref.getDocuments()[0].getTimestamp());
+ EXPECT_TRUE(!ref.getDocuments()[0].isRemoveEntry());
+ }
+ }
+ return true;
+}
+
+
+bool
+Messages50Test::testRemoveLocationMessage()
+{
+ {
+ document::BucketIdFactory factory;
+ document::select::Parser parser(getTypeRepo(), factory);
+ RemoveLocationMessage msg(factory, parser, "id.group == \"mygroup\"");
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 29u, serialize("RemoveLocationMessage", msg));
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("RemoveLocationMessage", DocumentProtocol::MESSAGE_REMOVELOCATION, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ RemoveLocationMessage &ref = static_cast<RemoveLocationMessage&>(*obj);
+ EXPECT_EQUAL(string("id.group == \"mygroup\""), ref.getDocumentSelection());
+ }
+ }
+ }
+
+ return true;
+}
+
+
+
+bool
+Messages50Test::testDocumentSummaryMessage()
+{
+ DocumentSummaryMessage srm;
+ EXPECT_EQUAL(srm.hasSequenceId(), false);
+ EXPECT_EQUAL(srm.getSummaryCount(), size_t(0));
+
+ mbus::Blob data = encode(srm);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + size_t(12), data.size());
+
+ writeFile(getPath("5-cpp-DocumentSummaryMessage-1.dat"), data);
+ // print(data);
+
+ mbus::Routable::UP routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_DOCUMENTSUMMARY);
+ DocumentSummaryMessage * dm = static_cast<DocumentSummaryMessage *>(routable.get());
+ EXPECT_EQUAL(dm->getSummaryCount(), size_t(0));
+
+ srm.addSummary("doc1", "summary1", 8);
+ srm.addSummary("aoc17", "summary45", 9);
+
+ data = encode(srm);
+ //print(data);
+
+ const void *summary(NULL);
+ const char *docId(NULL);
+ size_t sz(0);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 52u, data.size());
+ writeFile(getPath("5-cpp-DocumentSummaryMessage-2.dat"), data);
+ routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_DOCUMENTSUMMARY);
+ dm = static_cast<DocumentSummaryMessage *>(routable.get());
+ EXPECT_EQUAL(dm->getSummaryCount(), size_t(2));
+ dm->getSummary(0, docId, summary, sz);
+ EXPECT_EQUAL(sz, 8u);
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+ EXPECT_EQUAL(memcmp("summary1", summary, sz), 0);
+ dm->getSummary(1, docId, summary, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(strcmp("aoc17", docId), 0);
+ EXPECT_EQUAL(memcmp("summary45", summary, sz), 0);
+
+ srm.sort();
+
+ data = encode(srm);
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 52u, data.size());
+ writeFile(getPath("5-cpp-DocumentSummaryMessage-3.dat"), data);
+ routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_DOCUMENTSUMMARY);
+ dm = static_cast<DocumentSummaryMessage *>(routable.get());
+ EXPECT_EQUAL(dm->getSummaryCount(), size_t(2));
+ dm->getSummary(0, docId, summary, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(strcmp("aoc17", docId), 0);
+ EXPECT_EQUAL(memcmp("summary45", summary, sz), 0);
+ dm->getSummary(1, docId, summary, sz);
+ EXPECT_EQUAL(sz, 8u);
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+ EXPECT_EQUAL(memcmp("summary1", summary, sz), 0);
+ return true;
+}
+
+bool
+Messages50Test::testMultiOperationMessage()
+{
+ document::Document::SP doc =
+ createDoc(getTypeRepo(), "testdoc", "doc:scheme:foo");
+ std::vector<char> buffer(1024);
+ document::BucketIdFactory factory;
+
+ vdslib::WritableDocumentList doclist(getTypeRepoSp(),
+ &buffer[0], buffer.size());
+ ASSERT_TRUE(doclist.addPut(*doc));
+
+ size_t n = MESSAGE_BASE_LENGTH;
+ n += sizeof(uint32_t); // routable object type
+ n += sizeof(uint64_t); // bucket id
+ n += sizeof(uint32_t); // bytes in docblock
+ n += sizeof(uint32_t); // num operations
+ n += sizeof(vdslib::DocumentList::MetaEntry);
+ n += doc->serialize()->getLength();
+ n += 1; // boolean keepTimeStamps
+
+ MultiOperationMessage msg(document::BucketId(16, factory.getBucketId(doc->getId()).getRawId()), doclist);
+ EXPECT_EQUAL(n, serialize("MultiOperationMessage", msg));
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("MultiOperationMessage", DocumentProtocol::MESSAGE_MULTIOPERATION, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ MultiOperationMessage &ref = static_cast<MultiOperationMessage&>(*obj);
+ EXPECT_EQUAL((uint32_t)1, ref.getOperations().size());
+ EXPECT_EQUAL(*doc, *dynamic_cast<document::Document*>(ref.getOperations().begin()->getDocument().get()));
+ EXPECT_EQUAL(document::BucketId(16, factory.getBucketId(doc->getId()).getRawId()), ref.getBucketId());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testGetDocumentMessage()
+{
+ GetDocumentMessage tmp(document::DocumentId("doc:scheme:"), 0);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + (size_t)20, serialize("GetDocumentMessage", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("GetDocumentMessage", DocumentProtocol::MESSAGE_GETDOCUMENT, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ GetDocumentMessage &ref = static_cast<GetDocumentMessage&>(*obj);
+ EXPECT_EQUAL(string("doc:scheme:"), ref.getDocumentId().toString());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testMapVisitorMessage()
+{
+ MapVisitorMessage tmp;
+ tmp.getData().set("foo", 3);
+ tmp.getData().set("bar", 5);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + (size_t)32, serialize("MapVisitorMessage", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("MapVisitorMessage", DocumentProtocol::MESSAGE_MAPVISITOR, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ MapVisitorMessage &ref = static_cast<MapVisitorMessage&>(*obj);
+ EXPECT_EQUAL(3, ref.getData().get("foo", 0));
+ EXPECT_EQUAL(5, ref.getData().get("bar", 0));
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testCreateVisitorReply()
+{
+ CreateVisitorReply reply(DocumentProtocol::REPLY_CREATEVISITOR);
+ reply.setLastBucket(document::BucketId(16, 123));
+ vdslib::VisitorStatistics vs;
+ vs.setBucketsVisited(3);
+ vs.setDocumentsVisited(1000);
+ vs.setBytesVisited(1024000);
+ vs.setDocumentsReturned(123);
+ vs.setBytesReturned(512000);
+ vs.setSecondPassDocumentsReturned(456);
+ vs.setSecondPassBytesReturned(789100);
+ reply.setVisitorStatistics(vs);
+
+ EXPECT_EQUAL(65u, serialize("CreateVisitorReply", reply));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("CreateVisitorReply", DocumentProtocol::REPLY_CREATEVISITOR, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ CreateVisitorReply &ref = static_cast<CreateVisitorReply&>(*obj);
+
+ EXPECT_EQUAL(ref.getLastBucket(), document::BucketId(16, 123));
+ EXPECT_EQUAL(ref.getVisitorStatistics().getBucketsVisited(), (uint32_t)3);
+ EXPECT_EQUAL(ref.getVisitorStatistics().getDocumentsVisited(), (uint64_t)1000);
+ EXPECT_EQUAL(ref.getVisitorStatistics().getBytesVisited(), (uint64_t)1024000);
+ EXPECT_EQUAL(ref.getVisitorStatistics().getDocumentsReturned(), (uint64_t)123);
+ EXPECT_EQUAL(ref.getVisitorStatistics().getBytesReturned(), (uint64_t)512000);
+ EXPECT_EQUAL(ref.getVisitorStatistics().getSecondPassDocumentsReturned(), (uint64_t)456);
+ EXPECT_EQUAL(ref.getVisitorStatistics().getSecondPassBytesReturned(), (uint64_t)789100);
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testPutDocumentMessage()
+{
+ document::Document::SP doc =
+ createDoc(getTypeRepo(), "testdoc", "doc:scheme:");
+ PutDocumentMessage msg(doc);
+
+ msg.setTimestamp(666);
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 41u, serialize("PutDocumentMessage", msg));
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("PutDocumentMessage", DocumentProtocol::MESSAGE_PUTDOCUMENT, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ PutDocumentMessage &ref = static_cast<PutDocumentMessage&>(*obj);
+ EXPECT_TRUE(ref.getDocument()->getType().getName() == "testdoc");
+ EXPECT_TRUE(ref.getDocument()->getId().toString() == "doc:scheme:");
+ EXPECT_EQUAL(666u, ref.getTimestamp());
+ EXPECT_EQUAL(37u, ref.getApproxSize());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testGetBucketStateMessage()
+{
+ GetBucketStateMessage tmp;
+ tmp.setBucketId(document::BucketId(16, 666));
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 12u, serialize("GetBucketStateMessage", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("GetBucketStateMessage", DocumentProtocol::MESSAGE_GETBUCKETSTATE, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ GetBucketStateMessage &ref = static_cast<GetBucketStateMessage&>(*obj);
+
+ EXPECT_EQUAL(16u, ref.getBucketId().getUsedBits());
+ EXPECT_EQUAL(4611686018427388570ull, ref.getBucketId().getId());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testPutDocumentReply()
+{
+ WriteDocumentReply reply(DocumentProtocol::REPLY_PUTDOCUMENT);
+ reply.setHighestModificationTimestamp(30);
+
+ EXPECT_EQUAL(13u, serialize("PutDocumentReply", reply));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("PutDocumentReply", DocumentProtocol::REPLY_PUTDOCUMENT, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ WriteDocumentReply &ref = static_cast<WriteDocumentReply&>(*obj);
+ EXPECT_EQUAL(30u, ref.getHighestModificationTimestamp());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testUpdateDocumentReply()
+{
+ UpdateDocumentReply reply;
+ reply.setWasFound(false);
+ reply.setHighestModificationTimestamp(30);
+
+ EXPECT_EQUAL(14u, serialize("UpdateDocumentReply", reply));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("UpdateDocumentReply", DocumentProtocol::REPLY_UPDATEDOCUMENT, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ UpdateDocumentReply &ref = static_cast<UpdateDocumentReply&>(*obj);
+ EXPECT_EQUAL(30u, ref.getHighestModificationTimestamp());
+ EXPECT_EQUAL(false, ref.wasFound());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testRemoveDocumentMessage()
+{
+ RemoveDocumentMessage tmp(document::DocumentId("doc:scheme:"));
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + (size_t)16, serialize("RemoveDocumentMessage", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("RemoveDocumentMessage", DocumentProtocol::MESSAGE_REMOVEDOCUMENT, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ RemoveDocumentMessage &ref = static_cast<RemoveDocumentMessage&>(*obj);
+ EXPECT_EQUAL(string("doc:scheme:"), ref.getDocumentId().toString());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testRemoveDocumentReply()
+{
+ RemoveDocumentReply reply;
+ std::vector<uint64_t> ts;
+ reply.setWasFound(false);
+ reply.setHighestModificationTimestamp(30);
+
+ EXPECT_EQUAL(14u, serialize("RemoveDocumentReply", reply));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("RemoveDocumentReply", DocumentProtocol::REPLY_REMOVEDOCUMENT, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ RemoveDocumentReply &ref = static_cast<RemoveDocumentReply&>(*obj);
+ EXPECT_EQUAL(30u, ref.getHighestModificationTimestamp());
+ EXPECT_EQUAL(false, ref.wasFound());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testSearchResultMessage()
+{
+ SearchResultMessage srm;
+ EXPECT_EQUAL(srm.getSequenceId(), 0u);
+ EXPECT_EQUAL(srm.getHitCount(), 0u);
+ EXPECT_EQUAL(srm.getAggregatorList().getSerializedSize(), 4u);
+ EXPECT_EQUAL(srm.vdslib::SearchResult::getSerializedSize(), 20u);
+ EXPECT_EQUAL(srm.getSerializedSize(), 20u);
+
+ mbus::Blob data = encode(srm);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + size_t(24), data.size());
+
+ writeFile(getPath("5-cpp-SearchResultMessage-1.dat"), data);
+ // print(data);
+
+ mbus::Routable::UP routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_SEARCHRESULT);
+ SearchResultMessage * dm = static_cast<SearchResultMessage *>(routable.get());
+ EXPECT_EQUAL(dm->getSequenceId(), size_t(0));
+ EXPECT_EQUAL(dm->getHitCount(), size_t(0));
+
+ srm.addHit(0, "doc1", 89);
+ srm.addHit(1, "doc17", 109);
+ //srm.setSequenceId(567);
+
+ data = encode(srm);
+ //EXPECT_EQUAL(srm.getSequenceId(), size_t(567));
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 55u, data.size());
+ writeFile(getPath("5-cpp-SearchResultMessage-2.dat"), data);
+ routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_SEARCHRESULT);
+ dm = static_cast<SearchResultMessage *>(routable.get());
+// EXPECT_EQUAL(dm->getSequenceId(), size_t(567));
+ EXPECT_EQUAL(dm->getHitCount(), size_t(2));
+ const char *docId;
+ SearchResultMessage::RankType rank;
+ dm->getHit(0, docId, rank);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(89));
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+ dm->getHit(1, docId, rank);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(109));
+ EXPECT_EQUAL(strcmp("doc17", docId), 0);
+
+ srm.sort();
+
+ data = encode(srm);
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 55u, data.size());
+ writeFile(getPath("5-cpp-SearchResultMessage-3.dat"), data);
+ routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_SEARCHRESULT);
+ dm = static_cast<SearchResultMessage *>(routable.get());
+// EXPECT_EQUAL(dm->getSequenceId(), size_t(567));
+ EXPECT_EQUAL(dm->getHitCount(), size_t(2));
+ dm->getHit(0, docId, rank);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(109));
+ EXPECT_EQUAL(strcmp("doc17", docId), 0);
+ dm->getHit(1, docId, rank);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(89));
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+
+ SearchResultMessage srm2;
+ srm2.addHit(0, "doc1", 89, "sortdata2", 9);
+ srm2.addHit(1, "doc17", 109, "sortdata1", 9);
+ srm2.addHit(2, "doc18", 90, "sortdata3", 9);
+ //srm2.setSequenceId(567);
+ data = encode(srm2);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 108u, data.size());
+ writeFile(getPath("5-cpp-SearchResultMessage-4.dat"), data);
+ routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_SEARCHRESULT);
+ dm = static_cast<SearchResultMessage *>(routable.get());
+ //EXPECT_EQUAL(dm->getSequenceId(), size_t(567));
+ EXPECT_EQUAL(dm->getHitCount(), size_t(3));
+ dm->getHit(0, docId, rank);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(89));
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+ dm->getHit(1, docId, rank);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(109));
+ EXPECT_EQUAL(strcmp("doc17", docId), 0);
+ dm->getHit(2, docId, rank);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(90));
+ EXPECT_EQUAL(strcmp("doc18", docId), 0);
+
+ srm2.sort();
+ const void *buf;
+ size_t sz;
+ srm2.getHit(0, docId, rank);
+ srm2.getSortBlob(0, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata1", buf, sz), 0);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(109));
+ EXPECT_EQUAL(strcmp("doc17", docId), 0);
+ srm2.getHit(1, docId, rank);
+ srm2.getSortBlob(1, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata2", buf, sz), 0);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(89));
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+ srm2.getHit(2, docId, rank);
+ srm2.getSortBlob(2, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata3", buf, sz), 0);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(90));
+ EXPECT_EQUAL(strcmp("doc18", docId), 0);
+
+ data = encode(srm2);
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 108u, data.size());
+ writeFile(getPath("5-cpp-SearchResultMessage-5.dat"), data);
+ routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_SEARCHRESULT);
+ dm = static_cast<SearchResultMessage *>(routable.get());
+// EXPECT_EQUAL(dm->getSequenceId(), size_t(567));
+ EXPECT_EQUAL(dm->getHitCount(), size_t(3));
+ dm->getHit(0, docId, rank);
+ dm->getSortBlob(0, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata1", buf, sz), 0);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(109));
+ EXPECT_EQUAL(strcmp("doc17", docId), 0);
+ dm->getHit(1, docId, rank);
+ dm->getSortBlob(1, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata2", buf, sz), 0);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(89));
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+ dm->getHit(2, docId, rank);
+ dm->getSortBlob(2, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata3", buf, sz), 0);
+ EXPECT_EQUAL(rank, SearchResultMessage::RankType(90));
+ EXPECT_EQUAL(strcmp("doc18", docId), 0);
+ return true;
+}
+
+
+bool
+Messages50Test::testMultiOperationReply()
+{
+ WriteDocumentReply reply(DocumentProtocol::REPLY_MULTIOPERATION);
+ reply.setHighestModificationTimestamp(30);
+
+ EXPECT_EQUAL(13u, serialize("MultiOperationReply", reply));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("MultiOperationReply", DocumentProtocol::REPLY_MULTIOPERATION, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ WriteDocumentReply &ref = static_cast<WriteDocumentReply&>(*obj);
+ EXPECT_EQUAL(30u, ref.getHighestModificationTimestamp());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testUpdateDocumentMessage()
+{
+ const DocumentTypeRepo &repo = getTypeRepo();
+ const document::DocumentType &docType =
+ *repo.getDocumentType("testdoc");
+ document::DocumentUpdate::SP
+ upd(new document::DocumentUpdate(docType,
+ document::DocumentId("doc:scheme:")));
+ upd->addFieldPathUpdate(document::FieldPathUpdate::CP(
+ new document::RemoveFieldPathUpdate(repo, docType, "intfield", "testdoc.intfield > 0")));
+ UpdateDocumentMessage msg(upd);
+ msg.setOldTimestamp(666u);
+ msg.setNewTimestamp(777u);
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 89u, serialize("UpdateDocumentMessage", msg));
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("UpdateDocumentMessage", DocumentProtocol::MESSAGE_UPDATEDOCUMENT, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ UpdateDocumentMessage &ref = static_cast<UpdateDocumentMessage&>(*obj);
+ EXPECT_EQUAL(*upd, *ref.getDocumentUpdate());
+ EXPECT_EQUAL(666u, ref.getOldTimestamp());
+ EXPECT_EQUAL(777u, ref.getNewTimestamp());
+ EXPECT_EQUAL(85u, ref.getApproxSize());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testBatchDocumentUpdateMessage()
+{
+ const DocumentTypeRepo &repo = getTypeRepo();
+ const document::DocumentType &docType = *repo.getDocumentType("testdoc");
+
+ BatchDocumentUpdateMessage msg(1234);
+
+ {
+ document::DocumentUpdate::SP upd;
+ upd.reset(new document::DocumentUpdate(docType, document::DocumentId("userdoc:footype:1234:foo")));
+ upd->addFieldPathUpdate(document::FieldPathUpdate::CP(
+ new document::RemoveFieldPathUpdate(repo, docType, "intfield", "testdoc.intfield > 0")));
+ msg.addUpdate(upd);
+ }
+ {
+ document::DocumentUpdate::SP upd;
+ upd.reset(new document::DocumentUpdate(docType, document::DocumentId("orderdoc(32,17):footype:1234:123456789:foo")));
+ upd->addFieldPathUpdate(document::FieldPathUpdate::CP(
+ new document::RemoveFieldPathUpdate(repo, docType, "intfield", "testdoc.intfield > 0")));
+ msg.addUpdate(upd);
+ }
+ try {
+ document::DocumentUpdate::SP upd;
+ upd.reset(new document::DocumentUpdate(docType, document::DocumentId("userdoc:footype:5678:foo")));
+ upd->addFieldPathUpdate(document::FieldPathUpdate::CP(
+ new document::RemoveFieldPathUpdate(repo, docType, "intfield", "testdoc.intfield > 0")));
+ msg.addUpdate(upd);
+ EXPECT_TRUE(false);
+ } catch (...) {
+ }
+ try {
+ document::DocumentUpdate::SP upd;
+ upd.reset(new document::DocumentUpdate(docType, document::DocumentId("groupdoc:footype:hable:foo")));
+ upd->addFieldPathUpdate(document::FieldPathUpdate::CP(
+ new document::RemoveFieldPathUpdate(repo, docType, "intfield", "testdoc.intfield > 0")));
+ msg.addUpdate(upd);
+ EXPECT_TRUE(false);
+ } catch (...) {
+ }
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 202u, serialize("BatchDocumentUpdateMessage", msg));
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("BatchDocumentUpdateMessage", DocumentProtocol::MESSAGE_BATCHDOCUMENTUPDATE, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ BatchDocumentUpdateMessage &ref = static_cast<BatchDocumentUpdateMessage&>(*obj);
+ EXPECT_EQUAL(2u, ref.getUpdates().size());
+ }
+ }
+
+ return true;
+}
+
+bool
+Messages50Test::testBatchDocumentUpdateReply()
+{
+ BatchDocumentUpdateReply reply;
+ reply.setHighestModificationTimestamp(30);
+ {
+ std::vector<bool> notFound(3);
+ notFound[0] = false;
+ notFound[1] = true;
+ notFound[2] = true;
+ reply.getDocumentsNotFound() = notFound;
+ }
+
+ EXPECT_EQUAL(20u, serialize("BatchDocumentUpdateReply", reply));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("BatchDocumentUpdateReply", DocumentProtocol::REPLY_BATCHDOCUMENTUPDATE, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ BatchDocumentUpdateReply &ref = dynamic_cast<BatchDocumentUpdateReply&>(*obj);
+ EXPECT_EQUAL(30u, ref.getHighestModificationTimestamp());
+ {
+ const std::vector<bool>& notFound = ref.getDocumentsNotFound();
+ EXPECT_TRUE(notFound[0] == false);
+ EXPECT_TRUE(notFound[1] == true);
+ EXPECT_TRUE(notFound[2] == true);
+ }
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testQueryResultMessage()
+{
+ QueryResultMessage srm;
+ vdslib::SearchResult & sr(srm.getSearchResult());
+ EXPECT_EQUAL(srm.getSequenceId(), 0u);
+ EXPECT_EQUAL(sr.getHitCount(), 0u);
+ EXPECT_EQUAL(sr.getAggregatorList().getSerializedSize(), 4u);
+ EXPECT_EQUAL(sr.getSerializedSize(), 20u);
+ EXPECT_EQUAL(srm.getApproxSize(), 28u);
+
+ mbus::Blob data = encode(srm);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + size_t(32), data.size());
+
+ writeFile(getPath("5-cpp-QueryResultMessage-1.dat"), data);
+ // print(data);
+
+ mbus::Routable::UP routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_QUERYRESULT);
+ QueryResultMessage * dm = static_cast<QueryResultMessage *>(routable.get());
+ vdslib::SearchResult * dr(&dm->getSearchResult());
+ EXPECT_EQUAL(dm->getSequenceId(), size_t(0));
+ EXPECT_EQUAL(dr->getHitCount(), size_t(0));
+
+ sr.addHit(0, "doc1", 89);
+ sr.addHit(1, "doc17", 109);
+
+ data = encode(srm);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 63u, data.size());
+ writeFile(getPath("5-cpp-QueryResultMessage-2.dat"), data);
+ routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_QUERYRESULT);
+ dm = static_cast<QueryResultMessage *>(routable.get());
+ dr = &dm->getSearchResult();
+ EXPECT_EQUAL(dr->getHitCount(), size_t(2));
+ const char *docId;
+ vdslib::SearchResult::RankType rank;
+ dr->getHit(0, docId, rank);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(89));
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+ dr->getHit(1, docId, rank);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(109));
+ EXPECT_EQUAL(strcmp("doc17", docId), 0);
+
+ sr.sort();
+
+ data = encode(srm);
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 63u, data.size());
+ writeFile(getPath("5-cpp-QueryResultMessage-3.dat"), data);
+ routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_QUERYRESULT);
+ dm = static_cast<QueryResultMessage *>(routable.get());
+ dr = &dm->getSearchResult();
+ EXPECT_EQUAL(dr->getHitCount(), size_t(2));
+ dr->getHit(0, docId, rank);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(109));
+ EXPECT_EQUAL(strcmp("doc17", docId), 0);
+ dr->getHit(1, docId, rank);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(89));
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+
+ QueryResultMessage srm2;
+ vdslib::SearchResult & sr2(srm2.getSearchResult());
+ sr2.addHit(0, "doc1", 89, "sortdata2", 9);
+ sr2.addHit(1, "doc17", 109, "sortdata1", 9);
+ sr2.addHit(2, "doc18", 90, "sortdata3", 9);
+ data = encode(srm2);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 116u, data.size());
+ writeFile(getPath("5-cpp-QueryResultMessage-4.dat"), data);
+ routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_QUERYRESULT);
+ dm = static_cast<QueryResultMessage *>(routable.get());
+ dr = &dm->getSearchResult();
+ EXPECT_EQUAL(dr->getHitCount(), size_t(3));
+ dr->getHit(0, docId, rank);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(89));
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+ dr->getHit(1, docId, rank);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(109));
+ EXPECT_EQUAL(strcmp("doc17", docId), 0);
+ dr->getHit(2, docId, rank);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(90));
+ EXPECT_EQUAL(strcmp("doc18", docId), 0);
+
+ sr2.sort();
+ const void *buf;
+ size_t sz;
+ sr2.getHit(0, docId, rank);
+ sr2.getSortBlob(0, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata1", buf, sz), 0);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(109));
+ EXPECT_EQUAL(strcmp("doc17", docId), 0);
+ sr2.getHit(1, docId, rank);
+ sr2.getSortBlob(1, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata2", buf, sz), 0);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(89));
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+ sr2.getHit(2, docId, rank);
+ sr2.getSortBlob(2, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata3", buf, sz), 0);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(90));
+ EXPECT_EQUAL(strcmp("doc18", docId), 0);
+
+ data = encode(srm2);
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 116u, data.size());
+ writeFile(getPath("5-cpp-QueryResultMessage-5.dat"), data);
+ routable = decode(data);
+ if (!EXPECT_TRUE(routable.get() != NULL)) {
+ return false;
+ }
+ EXPECT_EQUAL(routable->getType(), (uint32_t)DocumentProtocol::MESSAGE_QUERYRESULT);
+ dm = static_cast<QueryResultMessage *>(routable.get());
+ dr = &dm->getSearchResult();
+ EXPECT_EQUAL(dr->getHitCount(), size_t(3));
+ dr->getHit(0, docId, rank);
+ dr->getSortBlob(0, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata1", buf, sz), 0);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(109));
+ EXPECT_EQUAL(strcmp("doc17", docId), 0);
+ dr->getHit(1, docId, rank);
+ dr->getSortBlob(1, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata2", buf, sz), 0);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(89));
+ EXPECT_EQUAL(strcmp("doc1", docId), 0);
+ dr->getHit(2, docId, rank);
+ dr->getSortBlob(2, buf, sz);
+ EXPECT_EQUAL(sz, 9u);
+ EXPECT_EQUAL(memcmp("sortdata3", buf, sz), 0);
+ EXPECT_EQUAL(rank, vdslib::SearchResult::RankType(90));
+ EXPECT_EQUAL(strcmp("doc18", docId), 0);
+ return true;
+}
+
+bool
+Messages50Test::testQueryResultReply()
+{
+ return tryVisitorReply("QueryResultReply", DocumentProtocol::REPLY_QUERYRESULT);
+}
+
+bool
+Messages50Test::testVisitorInfoMessage()
+{
+
+ VisitorInfoMessage tmp;
+ tmp.getFinishedBuckets().push_back(document::BucketId(16, 1));
+ tmp.getFinishedBuckets().push_back(document::BucketId(16, 2));
+ tmp.getFinishedBuckets().push_back(document::BucketId(16, 4));
+ string utf8 = "error message: \u00e6\u00c6\u00f8\u00d8\u00e5\u00c5\u00f6\u00d6";
+ tmp.setErrorMessage(utf8);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 67u, serialize("VisitorInfoMessage", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("VisitorInfoMessage", DocumentProtocol::MESSAGE_VISITORINFO, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ VisitorInfoMessage &ref = static_cast<VisitorInfoMessage&>(*obj);
+ EXPECT_EQUAL(document::BucketId(16, 1), ref.getFinishedBuckets()[0]);
+ EXPECT_EQUAL(document::BucketId(16, 2), ref.getFinishedBuckets()[1]);
+ EXPECT_EQUAL(document::BucketId(16, 4), ref.getFinishedBuckets()[2]);
+ EXPECT_EQUAL(utf8, ref.getErrorMessage());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testDestroyVisitorReply()
+{
+ return tryDocumentReply("DestroyVisitorReply", DocumentProtocol::REPLY_DESTROYVISITOR);
+}
+
+bool
+Messages50Test::testDocumentListReply()
+{
+ return tryVisitorReply("DocumentListReply", DocumentProtocol::REPLY_DOCUMENTLIST);
+}
+
+bool
+Messages50Test::testDocumentSummaryReply()
+{
+ return tryVisitorReply("DocumentSummaryReply", DocumentProtocol::REPLY_DOCUMENTSUMMARY);
+}
+
+bool
+Messages50Test::testGetDocumentReply()
+{
+ document::Document::SP doc =
+ createDoc(getTypeRepo(), "testdoc", "doc:scheme:");
+ GetDocumentReply tmp(doc);
+
+ EXPECT_EQUAL((size_t)43, serialize("GetDocumentReply", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("GetDocumentReply", DocumentProtocol::REPLY_GETDOCUMENT, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ GetDocumentReply &ref = static_cast<GetDocumentReply&>(*obj);
+
+ EXPECT_EQUAL(string("testdoc"), ref.getDocument()->getType().getName());
+ EXPECT_EQUAL(string("doc:scheme:"), ref.getDocument()->getId().toString());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testMapVisitorReply()
+{
+ return tryVisitorReply("MapVisitorReply", DocumentProtocol::REPLY_MAPVISITOR);
+}
+
+bool
+Messages50Test::testSearchResultReply()
+{
+ return tryVisitorReply("SearchResultReply", DocumentProtocol::REPLY_SEARCHRESULT);
+}
+
+bool
+Messages50Test::testStatBucketReply()
+{
+ StatBucketReply msg;
+ msg.setResults("These are the votes of the Norwegian jury");
+
+ EXPECT_EQUAL(50u, serialize("StatBucketReply", msg));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("StatBucketReply", DocumentProtocol::REPLY_STATBUCKET, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ StatBucketReply &ref = static_cast<StatBucketReply&>(*obj);
+ EXPECT_EQUAL("These are the votes of the Norwegian jury", ref.getResults());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testVisitorInfoReply()
+{
+ return tryVisitorReply("VisitorInfoReply", DocumentProtocol::REPLY_VISITORINFO);
+}
+
+bool
+Messages50Test::testWrongDistributionReply()
+{
+ WrongDistributionReply tmp("distributor:3 storage:2");
+
+ serialize("WrongDistributionReply", tmp);
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("WrongDistributionReply", DocumentProtocol::REPLY_WRONGDISTRIBUTION, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ WrongDistributionReply &ref = static_cast<WrongDistributionReply&>(*obj);
+ EXPECT_EQUAL(string("distributor:3 storage:2"), ref.getSystemState());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testGetBucketListReply()
+{
+ GetBucketListReply reply;
+ reply.getBuckets().push_back(GetBucketListReply::BucketInfo(document::BucketId(16, 123), "foo"));
+ reply.getBuckets().push_back(GetBucketListReply::BucketInfo(document::BucketId(17, 1123), "bar"));
+ reply.getBuckets().push_back(GetBucketListReply::BucketInfo(document::BucketId(18, 11123), "zoink"));
+
+ EXPECT_EQUAL(56u, serialize("GetBucketListReply", reply));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("GetBucketListReply", DocumentProtocol::REPLY_GETBUCKETLIST, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ GetBucketListReply &ref = static_cast<GetBucketListReply&>(*obj);
+
+ EXPECT_EQUAL(ref.getBuckets()[0], GetBucketListReply::BucketInfo(document::BucketId(16, 123), "foo"));
+ EXPECT_EQUAL(ref.getBuckets()[1], GetBucketListReply::BucketInfo(document::BucketId(17, 1123), "bar"));
+ EXPECT_EQUAL(ref.getBuckets()[2], GetBucketListReply::BucketInfo(document::BucketId(18, 11123), "zoink"));
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testGetBucketStateReply()
+{
+ document::GlobalId foo = document::DocumentId("doc:scheme:foo").getGlobalId();
+ document::GlobalId bar = document::DocumentId("doc:scheme:bar").getGlobalId();
+
+ GetBucketStateReply reply;
+ reply.getBucketState().push_back(DocumentState(foo, 777, false));
+ reply.getBucketState().push_back(DocumentState(bar, 888, true));
+ EXPECT_EQUAL(53u, serialize("GetBucketStateReply", reply));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("GetBucketStateReply", DocumentProtocol::REPLY_GETBUCKETSTATE, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ GetBucketStateReply &ref = static_cast<GetBucketStateReply&>(*obj);
+
+ EXPECT_EQUAL(777u, ref.getBucketState()[0].getTimestamp());
+ EXPECT_EQUAL(foo, ref.getBucketState()[0].getGlobalId());
+ EXPECT_EQUAL(false, ref.getBucketState()[0].isRemoveEntry());
+ EXPECT_EQUAL(888u, ref.getBucketState()[1].getTimestamp());
+ EXPECT_EQUAL(bar, ref.getBucketState()[1].getGlobalId());
+ EXPECT_EQUAL(true, ref.getBucketState()[1].isRemoveEntry());
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::testEmptyBucketsReply()
+{
+ return tryVisitorReply("EmptyBucketsReply", DocumentProtocol::REPLY_EMPTYBUCKETS);
+}
+
+bool
+Messages50Test::testRemoveLocationReply()
+{
+ DocumentReply tmp(DocumentProtocol::REPLY_REMOVELOCATION);
+
+ EXPECT_EQUAL((uint32_t)5, serialize("RemoveLocationReply", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("RemoveLocationReply", DocumentProtocol::REPLY_REMOVELOCATION, lang);
+ EXPECT_TRUE(obj.get() != NULL);
+ }
+ return true;
+}
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Utilities
+//
+////////////////////////////////////////////////////////////////////////////////
+
+bool
+Messages50Test::tryDocumentReply(const string &filename, uint32_t type)
+{
+ DocumentReply tmp(type);
+
+ EXPECT_EQUAL((uint32_t)5, serialize(filename, tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize(filename, type, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ DocumentReply *ref = dynamic_cast<DocumentReply*>(obj.get());
+ EXPECT_TRUE(ref != NULL);
+ }
+ }
+ return true;
+}
+
+bool
+Messages50Test::tryVisitorReply(const string &filename, uint32_t type)
+{
+ VisitorReply tmp(type);
+
+ EXPECT_EQUAL((uint32_t)5, serialize(filename, tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize(filename, type, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ VisitorReply *ref = dynamic_cast<VisitorReply*>(obj.get());
+ EXPECT_TRUE(ref != NULL);
+ }
+ }
+ return true;
+}
diff --git a/documentapi/src/tests/messages/messages50test.h b/documentapi/src/tests/messages/messages50test.h
new file mode 100644
index 00000000000..515a61e59fc
--- /dev/null
+++ b/documentapi/src/tests/messages/messages50test.h
@@ -0,0 +1,56 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "testbase.h"
+
+class Messages50Test : public TestBase {
+protected:
+ const vespalib::Version getVersion() const { return vespalib::Version(5, 0); }
+ bool shouldTestCoverage() const { return FALSE; }
+ bool tryDocumentReply(const string &filename, uint32_t type);
+ bool tryVisitorReply(const string &filename, uint32_t type);
+
+public:
+ Messages50Test();
+
+ bool testBatchDocumentUpdateMessage();
+ bool testBatchDocumentUpdateReply();
+ bool testCreateVisitorMessage();
+ bool testCreateVisitorReply();
+ bool testDestroyVisitorMessage();
+ bool testDestroyVisitorReply();
+ bool testDocumentListMessage();
+ bool testDocumentListReply();
+ bool testDocumentSummaryMessage();
+ bool testDocumentSummaryReply();
+ bool testEmptyBucketsMessage();
+ bool testEmptyBucketsReply();
+ bool testGetBucketListMessage();
+ bool testGetBucketListReply();
+ bool testGetBucketStateMessage();
+ bool testGetBucketStateReply();
+ bool testGetDocumentMessage();
+ bool testGetDocumentReply();
+ bool testMapVisitorMessage();
+ bool testMapVisitorReply();
+ bool testMultiOperationMessage();
+ bool testMultiOperationReply();
+ bool testPutDocumentMessage();
+ bool testPutDocumentReply();
+ bool testQueryResultMessage();
+ bool testQueryResultReply();
+ bool testRemoveDocumentMessage();
+ bool testRemoveDocumentReply();
+ bool testRemoveLocationMessage();
+ bool testRemoveLocationReply();
+ bool testSearchResultMessage();
+ bool testSearchResultReply();
+ bool testStatBucketMessage();
+ bool testStatBucketReply();
+ bool testUpdateDocumentMessage();
+ bool testUpdateDocumentReply();
+ bool testVisitorInfoMessage();
+ bool testVisitorInfoReply();
+ bool testWrongDistributionReply();
+};
+
diff --git a/documentapi/src/tests/messages/messages51app.cpp b/documentapi/src/tests/messages/messages51app.cpp
new file mode 100644
index 00000000000..6d68774f679
--- /dev/null
+++ b/documentapi/src/tests/messages/messages51app.cpp
@@ -0,0 +1,8 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP("messages51");
+
+#include "messages51test.h"
+
+TEST_APPHOOK(Messages51Test);
diff --git a/documentapi/src/tests/messages/messages51test.cpp b/documentapi/src/tests/messages/messages51test.cpp
new file mode 100644
index 00000000000..06a6becc45b
--- /dev/null
+++ b/documentapi/src/tests/messages/messages51test.cpp
@@ -0,0 +1,111 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP(".test");
+
+#include "messages51test.h"
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/document/update/fieldpathupdates.h>
+#include <vespa/documentapi/documentapi.h>
+#include <vespa/vdslib/container/writabledocumentlist.h>
+
+using document::DataType;
+using document::DocumentTypeRepo;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Setup
+//
+///////////////////////////////////////////////////////////////////////////////
+
+Messages51Test::Messages51Test()
+{
+ // This list MUST mirror the list of routable factories from the DocumentProtocol constructor that support
+ // version 5.0. When adding tests to this list, please KEEP THEM ORDERED alphabetically like they are now.
+ putTest(DocumentProtocol::MESSAGE_CREATEVISITOR, TEST_METHOD(Messages51Test::testCreateVisitorMessage));
+ putTest(DocumentProtocol::MESSAGE_GETDOCUMENT, TEST_METHOD(Messages51Test::testGetDocumentMessage));
+ putTest(DocumentProtocol::REPLY_DOCUMENTIGNORED, TEST_METHOD(Messages51Test::testDocumentIgnoredReply));
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Tests
+//
+///////////////////////////////////////////////////////////////////////////////
+
+static const int MESSAGE_BASE_LENGTH = 5;
+
+bool
+Messages51Test::testCreateVisitorMessage() {
+ CreateVisitorMessage tmp("SomeLibrary", "myvisitor", "newyork", "london");
+ tmp.setDocumentSelection("true and false or true");
+ tmp.getParameters().set("myvar", "somevalue");
+ tmp.getParameters().set("anothervar", uint64_t(34));
+ tmp.getBuckets().push_back(document::BucketId(16, 1234));
+ tmp.setVisitRemoves(true);
+ tmp.setFieldSet("foo bar");
+ tmp.setVisitorOrdering(document::OrderingSpecification::DESCENDING);
+ tmp.setMaxBucketsPerVisitor(2);
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + (size_t)178, serialize("CreateVisitorMessage", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("CreateVisitorMessage", DocumentProtocol::MESSAGE_CREATEVISITOR, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ CreateVisitorMessage &ref = static_cast<CreateVisitorMessage&>(*obj);
+
+ EXPECT_EQUAL(string("SomeLibrary"), ref.getLibraryName());
+ EXPECT_EQUAL(string("myvisitor"), ref.getInstanceId());
+ EXPECT_EQUAL(string("newyork"), ref.getControlDestination());
+ EXPECT_EQUAL(string("london"), ref.getDataDestination());
+ EXPECT_EQUAL(string("true and false or true"), ref.getDocumentSelection());
+ EXPECT_EQUAL(string("foo bar"), ref.getFieldSet());
+ EXPECT_EQUAL(uint32_t(8), ref.getMaximumPendingReplyCount());
+ EXPECT_EQUAL(true, ref.visitRemoves());
+ EXPECT_EQUAL(false, ref.visitHeadersOnly());
+ EXPECT_EQUAL(false, ref.visitInconsistentBuckets());
+ EXPECT_EQUAL(size_t(1), ref.getBuckets().size());
+ EXPECT_EQUAL(document::BucketId(16, 1234), ref.getBuckets()[0]);
+ EXPECT_EQUAL(string("somevalue"), ref.getParameters().get("myvar"));
+ EXPECT_EQUAL(uint64_t(34), ref.getParameters().get("anothervar", uint64_t(1)));
+ EXPECT_EQUAL(document::OrderingSpecification::DESCENDING, ref.getVisitorOrdering());
+ EXPECT_EQUAL(uint32_t(2), ref.getMaxBucketsPerVisitor());
+ }
+ }
+ return true;
+}
+
+bool
+Messages51Test::testGetDocumentMessage()
+{
+ GetDocumentMessage tmp(document::DocumentId("doc:scheme:"), "foo bar");
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + (size_t)27, serialize("GetDocumentMessage", tmp));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj = deserialize("GetDocumentMessage", DocumentProtocol::MESSAGE_GETDOCUMENT, lang);
+ if (EXPECT_TRUE(obj.get() != NULL)) {
+ GetDocumentMessage &ref = static_cast<GetDocumentMessage&>(*obj);
+ EXPECT_EQUAL(string("doc:scheme:"), ref.getDocumentId().toString());
+ EXPECT_EQUAL(string("foo bar"), ref.getFieldSet());
+ }
+ }
+ return true;
+}
+
+bool
+Messages51Test::testDocumentIgnoredReply()
+{
+ DocumentIgnoredReply tmp;
+ serialize("DocumentIgnoredReply", tmp);
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ mbus::Routable::UP obj(
+ deserialize("DocumentIgnoredReply",
+ DocumentProtocol::REPLY_DOCUMENTIGNORED, lang));
+ EXPECT_TRUE(obj.get() != NULL);
+ }
+ return true;
+}
diff --git a/documentapi/src/tests/messages/messages51test.h b/documentapi/src/tests/messages/messages51test.h
new file mode 100644
index 00000000000..9cf57a44b29
--- /dev/null
+++ b/documentapi/src/tests/messages/messages51test.h
@@ -0,0 +1,18 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include "messages50test.h"
+
+class Messages51Test : public Messages50Test {
+protected:
+ const vespalib::Version getVersion() const { return vespalib::Version(5, 1); }
+ bool shouldTestCoverage() const { return TRUE; }
+
+public:
+ Messages51Test();
+
+ bool testCreateVisitorMessage();
+ bool testGetDocumentMessage();
+ bool testDocumentIgnoredReply();
+};
+
diff --git a/documentapi/src/tests/messages/messages52app.cpp b/documentapi/src/tests/messages/messages52app.cpp
new file mode 100644
index 00000000000..15fb603524b
--- /dev/null
+++ b/documentapi/src/tests/messages/messages52app.cpp
@@ -0,0 +1,8 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP("messages52");
+
+#include "messages52test.h"
+
+TEST_APPHOOK(Messages52Test);
diff --git a/documentapi/src/tests/messages/messages52test.cpp b/documentapi/src/tests/messages/messages52test.cpp
new file mode 100644
index 00000000000..f3625150511
--- /dev/null
+++ b/documentapi/src/tests/messages/messages52test.cpp
@@ -0,0 +1,122 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// @author Vegard Sjonfjell
+#include <vespa/fastos/fastos.h>
+#include <vespa/log/log.h>
+LOG_SETUP(".test");
+
+#include "messages52test.h"
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/document/update/fieldpathupdates.h>
+#include <vespa/documentapi/documentapi.h>
+
+using document::DocumentTypeRepo;
+
+namespace {
+
+document::Document::SP
+createDoc(const DocumentTypeRepo &repo, const string &type_name, const string &id)
+{
+ return document::Document::SP(new document::Document(
+ *repo.getDocumentType(type_name),
+ document::DocumentId(id)));
+}
+
+}
+
+static const int MESSAGE_BASE_LENGTH = 5;
+
+Messages52Test::Messages52Test()
+{
+ // This list MUST mirror the list of routable factories from the DocumentProtocol constructor that support
+ // version 5.2. When adding tests to this list, please KEEP THEM ORDERED alphabetically like they are now.
+
+ putTest(DocumentProtocol::MESSAGE_PUTDOCUMENT, TEST_METHOD(Messages52Test::testPutDocumentMessage));
+ putTest(DocumentProtocol::MESSAGE_REMOVEDOCUMENT, TEST_METHOD(Messages52Test::testRemoveDocumentMessage));
+ putTest(DocumentProtocol::MESSAGE_UPDATEDOCUMENT, TEST_METHOD(Messages52Test::testUpdateDocumentMessage));
+}
+
+bool
+Messages52Test::testPutDocumentMessage()
+{
+ auto doc = createDoc(getTypeRepo(), "testdoc", "doc:scheme:");
+ PutDocumentMessage msg(doc);
+
+ msg.setTimestamp(666);
+ msg.setCondition(TestAndSetCondition("There's just one condition"));
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH +
+ 41u +
+ serializedLength(msg.getCondition().getSelection()),
+ serialize("PutDocumentMessage", msg));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ auto routableUp = deserialize("PutDocumentMessage", DocumentProtocol::MESSAGE_PUTDOCUMENT, lang);
+ if (EXPECT_TRUE(routableUp.get() != nullptr)) {
+ auto & deserializedMsg = static_cast<PutDocumentMessage &>(*routableUp);
+
+ EXPECT_EQUAL(msg.getDocument()->getType().getName(), deserializedMsg.getDocument()->getType().getName());
+ EXPECT_EQUAL(msg.getDocument()->getId().toString(), deserializedMsg.getDocument()->getId().toString());
+ EXPECT_EQUAL(msg.getTimestamp(), deserializedMsg.getTimestamp());
+ EXPECT_EQUAL(67u, deserializedMsg.getApproxSize());
+ EXPECT_EQUAL(msg.getCondition().getSelection(), deserializedMsg.getCondition().getSelection());
+ }
+ }
+
+ return true;
+}
+
+bool
+Messages52Test::testRemoveDocumentMessage()
+{
+ RemoveDocumentMessage msg(document::DocumentId("doc:scheme:"));
+
+ msg.setCondition(TestAndSetCondition("There's just one condition"));
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + size_t(16) + serializedLength(msg.getCondition().getSelection()), serialize("RemoveDocumentMessage", msg));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ auto routablePtr = deserialize("RemoveDocumentMessage", DocumentProtocol::MESSAGE_REMOVEDOCUMENT, lang);
+
+ if (EXPECT_TRUE(routablePtr.get() != nullptr)) {
+ auto & ref = static_cast<RemoveDocumentMessage &>(*routablePtr);
+ EXPECT_EQUAL(string("doc:scheme:"), ref.getDocumentId().toString());
+ EXPECT_EQUAL(msg.getCondition().getSelection(), ref.getCondition().getSelection());
+ }
+ }
+ return true;
+}
+
+bool
+Messages52Test::testUpdateDocumentMessage()
+{
+ const DocumentTypeRepo & repo = getTypeRepo();
+ const document::DocumentType & docType = *repo.getDocumentType("testdoc");
+
+ document::DocumentUpdate::SP docUpdate(new document::DocumentUpdate(docType,
+ document::DocumentId("doc:scheme:")));
+
+ docUpdate->addFieldPathUpdate(document::FieldPathUpdate::CP(
+ new document::RemoveFieldPathUpdate(repo, docType, "intfield", "testdoc.intfield > 0")));
+
+ UpdateDocumentMessage msg(docUpdate);
+ msg.setOldTimestamp(666u);
+ msg.setNewTimestamp(777u);
+ msg.setCondition(TestAndSetCondition("There's just one condition"));
+
+ EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 89u + serializedLength(msg.getCondition().getSelection()), serialize("UpdateDocumentMessage", msg));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ auto routableUp = deserialize("UpdateDocumentMessage", DocumentProtocol::MESSAGE_UPDATEDOCUMENT, lang);
+
+ if (EXPECT_TRUE(routableUp.get() != nullptr)) {
+ auto & deserializedMsg = static_cast<UpdateDocumentMessage &>(*routableUp);
+ EXPECT_EQUAL(*msg.getDocumentUpdate(), *deserializedMsg.getDocumentUpdate());
+ EXPECT_EQUAL(msg.getOldTimestamp(), deserializedMsg.getOldTimestamp());
+ EXPECT_EQUAL(msg.getNewTimestamp(), deserializedMsg.getNewTimestamp());
+ EXPECT_EQUAL(115u, deserializedMsg.getApproxSize());
+ EXPECT_EQUAL(msg.getCondition().getSelection(), deserializedMsg.getCondition().getSelection());
+ }
+ }
+ return true;
+}
diff --git a/documentapi/src/tests/messages/messages52test.h b/documentapi/src/tests/messages/messages52test.h
new file mode 100644
index 00000000000..71e3d54902c
--- /dev/null
+++ b/documentapi/src/tests/messages/messages52test.h
@@ -0,0 +1,23 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// @author Vegard Sjonfjell
+#pragma once
+
+#include "messages51test.h"
+
+class Messages52Test : public Messages51Test {
+protected:
+ const vespalib::Version getVersion() const override { return vespalib::Version(5, 115, 0); }
+
+public:
+ Messages52Test();
+
+ bool testPutDocumentMessage();
+ bool testUpdateDocumentMessage();
+ bool testRemoveDocumentMessage();
+
+private:
+ static size_t serializedLength(const string & str) {
+ return sizeof(int32_t) + str.size();
+ }
+};
+
diff --git a/documentapi/src/tests/messages/testbase.cpp b/documentapi/src/tests/messages/testbase.cpp
new file mode 100644
index 00000000000..a6aeefd883f
--- /dev/null
+++ b/documentapi/src/tests/messages/testbase.cpp
@@ -0,0 +1,197 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP(".testbase");
+
+#include "testbase.h"
+#include <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/repo/documenttyperepo.h>
+
+using document::DocumentTypeRepo;
+using document::readDocumenttypesConfig;
+
+TestBase::TestBase() :
+ _repo(new DocumentTypeRepo(readDocumenttypesConfig("../../../test/cfg/testdoctypes.cfg"))),
+ _dataPath("../../../test/crosslanguagefiles"),
+ _loadTypes(),
+ _protocol(_loadTypes, _repo),
+ _tests()
+{
+ _loadTypes.addLoadType(34, "foo", Priority::PRI_NORMAL_2);
+}
+
+int
+TestBase::Main()
+{
+ TEST_INIT("messages_test");
+
+ // Retrieve version number to test for.
+ LOG(info, "Running tests for version %s.", getVersion().toString().c_str());
+
+ // Run registered tests.
+ for (std::map<uint32_t, TEST_METHOD_PT>::iterator it = _tests.begin();
+ it != _tests.end(); ++it)
+ {
+ LOG(info, "Running test for routable type %d.", it->first);
+ EXPECT_TRUE( (this->*(it->second))() );
+ TEST_FLUSH();
+ }
+
+ // Test routable type coverage.
+ std::vector<uint32_t> expected, actual;
+ EXPECT_TRUE(testCoverage(expected, actual));
+ expected.push_back(0);
+ EXPECT_TRUE(!testCoverage(expected, actual));
+ actual.push_back(1);
+ EXPECT_TRUE(!testCoverage(expected, actual));
+ actual.push_back(0);
+ EXPECT_TRUE(!testCoverage(expected, actual));
+ expected.push_back(1);
+ EXPECT_TRUE(testCoverage(expected, actual));
+
+ expected.clear();
+ _protocol.getRoutableTypes(getVersion(), expected);
+
+ actual.clear();
+ for (std::map<uint32_t, TEST_METHOD_PT>::iterator it = _tests.begin();
+ it != _tests.end(); ++it)
+ {
+ actual.push_back(it->first);
+ }
+ if (shouldTestCoverage()) {
+ EXPECT_TRUE(testCoverage(expected, actual, true));
+ }
+ TEST_DONE();
+}
+
+TestBase &
+TestBase::putTest(uint32_t type, TEST_METHOD_PT test)
+{
+ _tests[type] = test;
+ return *this;
+}
+
+bool
+TestBase::testCoverage(const std::vector<uint32_t> &expected, const std::vector<uint32_t> &actual, bool report) const
+{
+ bool ret = true;
+
+ std::vector<uint32_t> lst(actual);
+ for (std::vector<uint32_t>::const_iterator it = expected.begin();
+ it != expected.end(); ++it)
+ {
+ std::vector<uint32_t>::iterator occ = std::find(lst.begin(), lst.end(), *it);
+ if (occ == lst.end()) {
+ if (report) {
+ LOG(error, "Routable type %d is registered in DocumentProtocol but not tested.", *it);
+ }
+ ret = false;
+ } else {
+ lst.erase(occ);
+ }
+ }
+ if (!lst.empty()) {
+ if (report) {
+ for (std::vector<uint32_t>::iterator it = lst.begin();
+ it != lst.end(); ++it)
+ {
+ LOG(error, "Routable type %d is tested but not registered in DocumentProtocol.", *it);
+ }
+ }
+ ret = false;
+ }
+
+ return ret;
+}
+
+uint32_t
+TestBase::serialize(const string &filename, const mbus::Routable &routable)
+{
+ const vespalib::Version version = getVersion();
+ string path = getPath(version.toString() + "-cpp-" + filename + ".dat");
+ LOG(info, "Serializing to '%s'..", path.c_str());
+
+ mbus::Blob blob = _protocol.encode(version, routable);
+ if (!EXPECT_TRUE(writeFile(path, blob))) {
+ LOG(error, "Could not open file '%s' for writing.", path.c_str());
+ return 0;
+ }
+ mbus::Routable::UP obj = _protocol.decode(version, blob);
+ if (!EXPECT_TRUE(obj.get() != NULL)) {
+ LOG(error, "Protocol failed to decode serialized data.");
+ return 0;
+ }
+ if (!EXPECT_TRUE(routable.getType() == obj->getType())) {
+ LOG(error, "Expected class %d, got %d.", routable.getType(), obj->getType());
+ return 0;
+ }
+ return blob.size();
+}
+
+mbus::Routable::UP
+TestBase::deserialize(const string &filename, uint32_t classId, uint32_t lang)
+{
+ const vespalib::Version version = getVersion();
+ string path = getPath(version.toString() + (lang == LANG_JAVA ? "-java" : "-cpp") + "-" + filename + ".dat");
+ LOG(info, "Deserializing from '%s'..", path.c_str());
+
+ mbus::Blob blob = readFile(path);
+ if (!EXPECT_TRUE(blob.size() != 0)) {
+ LOG(error, "Could not open file '%s' for reading.", path.c_str());
+ return mbus::Routable::UP();
+ }
+ mbus::Routable::UP ret = _protocol.decode(version, blob);
+
+ if (!EXPECT_TRUE(ret.get())) {
+ LOG(error, "Unable to decode class %d", classId);
+ } else if (!EXPECT_TRUE(classId == ret->getType())) {
+ LOG(error, "Expected class %d, got %d.", classId, ret->getType());
+ return mbus::Routable::UP();
+ }
+ return ret;
+}
+
+void
+TestBase::dump(const mbus::Blob& blob) const
+{
+ fprintf(stderr, "[%ld]: ", blob.size());
+ for(size_t i = 0; i < blob.size(); i++) {
+ if (blob.data()[i] > 32 && blob.data()[i] < 126) {
+ fprintf(stderr, "%c ", blob.data()[i]);
+ }
+ else {
+ fprintf(stderr, "%d ", blob.data()[i]);
+ }
+ }
+ fprintf(stderr, "\n");
+}
+
+
+bool
+TestBase::writeFile(const string &filename, const mbus::Blob& blob) const
+{
+ int file = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (file == -1) {
+ return false;
+ }
+ write(file, blob.data(), blob.size());
+ close(file);
+ return true;
+}
+
+mbus::Blob
+TestBase::readFile(const string &filename) const
+{
+ int file = open(filename.c_str(), O_RDONLY);
+ int len = (file == -1) ? 0 : lseek(file, 0, SEEK_END);
+ mbus::Blob blob(len);
+ if (file != -1) {
+ lseek(file, 0, SEEK_SET);
+ read(file, blob.data(), len);
+ close(file);
+ }
+
+ return blob;
+}
+
+
diff --git a/documentapi/src/tests/messages/testbase.h b/documentapi/src/tests/messages/testbase.h
new file mode 100644
index 00000000000..5ebe154cb94
--- /dev/null
+++ b/documentapi/src/tests/messages/testbase.h
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/documentapi/messagebus/documentprotocol.h>
+#include <vespa/messagebus/routable.h>
+#include <vespa/vespalib/component/version.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace documentapi;
+
+/**
+ * Declare the signature of the test method.
+ */
+class TestBase;
+typedef bool (TestBase::*TEST_METHOD_PT)();
+#define TEST_METHOD(pt) ((TEST_METHOD_PT)&pt)
+
+/**
+ * This is the test base itself. It offers a set of utility functions that reflect on the version returned by
+ * the pure virtual getVersion() function. You need to inherit this and assign a version and a set of message
+ * tests to it.
+ */
+class TestBase : public vespalib::TestApp {
+ const document::DocumentTypeRepo::SP _repo;
+protected:
+ const string _dataPath;
+ LoadTypeSet _loadTypes;
+ DocumentProtocol _protocol;
+ std::map<uint32_t, TEST_METHOD_PT> _tests;
+
+ // Declares what languages share serialization.
+ enum {
+ LANG_CPP = 0,
+ LANG_JAVA,
+ NUM_LANGUAGES
+ };
+
+ TestBase();
+ virtual ~TestBase() { /* empty */ }
+ virtual const vespalib::Version getVersion() const = 0;
+ virtual bool shouldTestCoverage() const = 0;
+ TestBase &putTest(uint32_t type, TEST_METHOD_PT test);
+ int Main();
+
+public:
+ const document::DocumentTypeRepo &getTypeRepo() { return *_repo; }
+ const document::DocumentTypeRepo::SP &getTypeRepoSp() { return _repo; }
+
+ bool testCoverage(const std::vector<uint32_t> &expected, const std::vector<uint32_t> &actual, bool report = false) const;
+ bool writeFile(const string &filename, const mbus::Blob& blob) const;
+ mbus::Blob readFile(const string &filename) const;
+ uint32_t serialize(const string &filename, const mbus::Routable &routable);
+ mbus::Routable::UP deserialize(const string &filename, uint32_t classId, uint32_t lang);
+ void dump(const mbus::Blob &blob) const;
+
+ string getPath(const string &filename) const { return _dataPath + "/" + filename; }
+ mbus::Blob encode(const mbus::Routable &obj) const { return _protocol.encode(getVersion(), obj); }
+ mbus::Routable::UP decode(mbus::BlobRef data) const { return _protocol.decode(getVersion(), data); }
+};
+
diff --git a/documentapi/src/tests/policies/.gitignore b/documentapi/src/tests/policies/.gitignore
new file mode 100644
index 00000000000..c92767a6536
--- /dev/null
+++ b/documentapi/src/tests/policies/.gitignore
@@ -0,0 +1,5 @@
+*_test
+.depend
+Makefile
+log
+documentapi_policies_test_app
diff --git a/documentapi/src/tests/policies/CMakeLists.txt b/documentapi/src/tests/policies/CMakeLists.txt
new file mode 100644
index 00000000000..dcf0f4bef4b
--- /dev/null
+++ b/documentapi/src/tests/policies/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(documentapi_policies_test_app
+ SOURCES
+ testframe.cpp
+ policies_test.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_policies_test_app COMMAND documentapi_policies_test_app)
diff --git a/documentapi/src/tests/policies/policies_test.cpp b/documentapi/src/tests/policies/policies_test.cpp
new file mode 100644
index 00000000000..1cab15a325f
--- /dev/null
+++ b/documentapi/src/tests/policies/policies_test.cpp
@@ -0,0 +1,1252 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP("policies_test");
+
+#include <vespa/document/config/config-documenttypes.h>
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/fieldvalue/document.h>
+#include <vespa/document/fieldvalue/longfieldvalue.h>
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/documentapi/documentapi.h>
+#include <vespa/documentapi/messagebus/policies/andpolicy.h>
+#include <vespa/documentapi/messagebus/policies/documentrouteselectorpolicy.h>
+#include <vespa/documentapi/messagebus/policies/errorpolicy.h>
+#include <vespa/documentapi/messagebus/policies/externpolicy.h>
+#include <vespa/documentapi/messagebus/policies/loadbalancerpolicy.h>
+#include <vespa/documentapi/messagebus/policies/localservicepolicy.h>
+#include <vespa/documentapi/messagebus/policies/roundrobinpolicy.h>
+#include <vespa/documentapi/messagebus/policies/searchcolumnpolicy.h>
+#include <vespa/documentapi/messagebus/policies/searchrowpolicy.h>
+#include <vespa/documentapi/messagebus/policies/storagepolicy.h>
+#include <vespa/documentapi/messagebus/policies/subsetservicepolicy.h>
+#include <vespa/documentapi/messagebus/systemstate/systemstatehandle.h>
+#include <limits>
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/errorcode.h>
+#include <vespa/messagebus/routing/routingcontext.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vdslib/container/mutabledocumentlist.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include "testframe.h"
+
+using document::DataType;
+using document::Document;
+using document::DocumentId;
+using document::DocumentTypeRepo;
+using document::DocumentUpdate;
+using document::readDocumenttypesConfig;
+using slobrok::api::MirrorAPI;
+using namespace documentapi;
+
+class Test : public vespalib::TestApp {
+private:
+ LoadTypeSet _loadTypes;
+ DocumentTypeRepo::SP _repo;
+ const DataType *_docType;
+
+private:
+ bool trySelect(TestFrame &frame, uint32_t numSelects, const std::vector<string> &expected);
+ bool tryDistribution(TestFrame &frame, const string &id, const string &expected);
+ void tryWasFound(TestFrame &frame, uint32_t expectedRecipients,
+ uint32_t foundMask, bool expectedFound);
+ void setupExternPolicy(TestFrame &frame, mbus::Slobrok &slobrok, const string &pattern,
+ int32_t numEntries = -1);
+ StoragePolicy &setupStoragePolicy(TestFrame &frame, const string &param,
+ const string &pattern = "", int32_t numEntries = -1);
+ bool isErrorPolicy(const string &name, const string &param);
+ void assertMirrorReady(const slobrok::api::MirrorAPI &mirror);
+ void assertMirrorContains(const slobrok::api::MirrorAPI &mirror, const string &pattern,
+ uint32_t numEntries);
+ mbus::Message::UP newPutDocumentMessage(const string &documentId);
+
+public:
+ int Main();
+ void testAND();
+ void testDocumentRouteSelector();
+ void testDocumentRouteSelectorIgnore();
+ void testExternSend();
+ void testExternMultipleSlobroks();
+ void testLoadBalancer();
+ void testLocalService();
+ void testLocalServiceCache();
+ void testProtocol();
+ void testRoundRobin();
+ void testRoundRobinCache();
+ void testSearchColumn();
+ void testSearchRow();
+ void testSearchRowMerge();
+ void multipleGetRepliesAreMergedToFoundDocument();
+ void testSubsetService();
+ void testSubsetServiceCache();
+
+ void requireThatExternPolicyWithIllegalParamIsAnErrorPolicy();
+ void requireThatExternPolicyWithUnknownPatternSelectsNone();
+ void requireThatExternPolicySelectsFromExternSlobrok();
+ void requireThatExternPolicyMergesOneReplyAsProtocol();
+ void requireThatStoragePolicyWithIllegalParamIsAnErrorPolicy();
+ void requireThatStoragePolicyIsRandomWithoutState();
+ void requireThatStoragePolicyIsTargetedWithState();
+ void requireThatStoragePolicyCombinesSystemAndSlobrokState();
+};
+
+TEST_APPHOOK(Test);
+
+int
+Test::Main() {
+ TEST_INIT(_argv[0]);
+
+ _repo.reset(new DocumentTypeRepo(readDocumenttypesConfig(
+ "../../../test/cfg/testdoctypes.cfg")));
+ _docType = _repo->getDocumentType("testdoc");
+
+ testProtocol(); TEST_FLUSH();
+
+ testAND(); TEST_FLUSH();
+ testDocumentRouteSelector(); TEST_FLUSH();
+ testDocumentRouteSelectorIgnore(); TEST_FLUSH();
+ testExternSend(); TEST_FLUSH();
+ testExternMultipleSlobroks(); TEST_FLUSH();
+ testLoadBalancer(); TEST_FLUSH();
+ testLocalService(); TEST_FLUSH();
+ testLocalServiceCache(); TEST_FLUSH();
+ testRoundRobin(); TEST_FLUSH();
+ testRoundRobinCache(); TEST_FLUSH();
+ testSearchColumn(); TEST_FLUSH();
+ testSearchRow(); TEST_FLUSH();
+ testSearchRowMerge(); TEST_FLUSH();
+ testSubsetService(); TEST_FLUSH();
+ testSubsetServiceCache(); TEST_FLUSH();
+
+ multipleGetRepliesAreMergedToFoundDocument(); TEST_FLUSH();
+
+ requireThatExternPolicyWithIllegalParamIsAnErrorPolicy(); TEST_FLUSH();
+ requireThatExternPolicyWithUnknownPatternSelectsNone(); TEST_FLUSH();
+ requireThatExternPolicySelectsFromExternSlobrok(); TEST_FLUSH();
+ requireThatExternPolicyMergesOneReplyAsProtocol(); TEST_FLUSH();
+
+ requireThatStoragePolicyWithIllegalParamIsAnErrorPolicy(); TEST_FLUSH();
+ requireThatStoragePolicyIsRandomWithoutState(); TEST_FLUSH();
+ requireThatStoragePolicyIsTargetedWithState(); TEST_FLUSH();
+ requireThatStoragePolicyCombinesSystemAndSlobrokState(); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+void
+Test::testProtocol()
+{
+ mbus::IProtocol::SP protocol(new DocumentProtocol(_loadTypes, _repo));
+
+ mbus::IRoutingPolicy::UP policy = protocol->createPolicy("AND", "");
+ ASSERT_TRUE(dynamic_cast<ANDPolicy*>(policy.get()) != NULL);
+
+ policy = protocol->createPolicy("DocumentRouteSelector", "raw:route[0]\n");
+ ASSERT_TRUE(dynamic_cast<DocumentRouteSelectorPolicy*>(policy.get()) != NULL);
+
+ policy = protocol->createPolicy("Extern", "foo;bar/baz");
+ ASSERT_TRUE(dynamic_cast<ExternPolicy*>(policy.get()) != NULL);
+
+ policy = protocol->createPolicy("LoadBalancer",
+ "cluster=docproc/cluster.default;"
+ "session=chain.default;syncinit");
+ ASSERT_TRUE(dynamic_cast<LoadBalancerPolicy*>(policy.get()) != NULL);
+
+ policy = protocol->createPolicy("LocalService", "");
+ ASSERT_TRUE(dynamic_cast<LocalServicePolicy*>(policy.get()) != NULL);
+
+ policy = protocol->createPolicy("RoundRobin", "");
+ ASSERT_TRUE(dynamic_cast<RoundRobinPolicy*>(policy.get()) != NULL);
+
+ policy = protocol->createPolicy("SearchRow", "");
+ ASSERT_TRUE(dynamic_cast<SearchRowPolicy*>(policy.get()) != NULL);
+
+ policy = protocol->createPolicy("SearchColumn", "");
+ ASSERT_TRUE(dynamic_cast<SearchColumnPolicy*>(policy.get()) != NULL);
+
+ policy = protocol->createPolicy("SubsetService", "");
+ ASSERT_TRUE(dynamic_cast<SubsetServicePolicy*>(policy.get()) != NULL);
+}
+
+void
+Test::testAND()
+{
+ TestFrame frame(_repo);
+ frame.setMessage(mbus::Message::UP(new PutDocumentMessage(
+ document::Document::SP(
+ new document::Document(*_docType,
+ DocumentId("doc:scheme:"))))));
+ frame.setHop(mbus::HopSpec("test", "[AND]")
+ .addRecipient("foo")
+ .addRecipient("bar"));
+ EXPECT_TRUE(frame.testSelect(StringList().add("foo").add("bar")));
+
+ frame.setHop(mbus::HopSpec("test", "[AND:baz]")
+ .addRecipient("foo").addRecipient("bar"));
+ EXPECT_TRUE(frame.testSelect(StringList().add("baz"))); // param precedes recipients
+
+ frame.setHop(mbus::HopSpec("test", "[AND:foo]"));
+ EXPECT_TRUE(frame.testMergeOneReply("foo"));
+
+ frame.setHop(mbus::HopSpec("test", "[AND:foo bar]"));
+ EXPECT_TRUE(frame.testMergeTwoReplies("foo", "bar"));
+}
+
+void
+Test::requireThatExternPolicyWithIllegalParamIsAnErrorPolicy()
+{
+ mbus::Slobrok slobrok;
+ string spec = vespalib::make_string("tcp/localhost:%d", slobrok.port());
+
+ EXPECT_TRUE(isErrorPolicy("Extern", ""));
+ EXPECT_TRUE(isErrorPolicy("Extern", spec));
+ EXPECT_TRUE(isErrorPolicy("Extern", spec + ";"));
+ EXPECT_TRUE(isErrorPolicy("Extern", spec + ";bar"));
+}
+
+void
+Test::requireThatExternPolicyWithUnknownPatternSelectsNone()
+{
+ TestFrame frame(_repo);
+ frame.setMessage(newPutDocumentMessage("doc:scheme:"));
+
+ mbus::Slobrok slobrok;
+ setupExternPolicy(frame, slobrok, "foo/bar");
+ EXPECT_TRUE(frame.testSelect(StringList()));
+}
+
+void
+Test::requireThatExternPolicySelectsFromExternSlobrok()
+{
+ TestFrame frame(_repo);
+ frame.setMessage(newPutDocumentMessage("doc:scheme:"));
+ mbus::Slobrok slobrok;
+ std::vector<mbus::TestServer*> servers;
+ for (uint32_t i = 0; i < 10; ++i) {
+ mbus::TestServer *server = new mbus::TestServer(
+ mbus::Identity(vespalib::make_string("docproc/cluster.default/%d", i)),
+ mbus::RoutingSpec(), slobrok, "",
+ mbus::IProtocol::SP(new DocumentProtocol(_loadTypes, _repo)));
+ servers.push_back(server);
+ server->net.registerSession("chain.default");
+ }
+ setupExternPolicy(frame, slobrok, "docproc/cluster.default/*/chain.default", 10);
+ std::set<string> lst;
+ for (uint32_t i = 0; i < servers.size(); ++i) {
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 1));
+ lst.insert(leaf[0]->getRoute().toString());
+
+ leaf[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ ASSERT_TRUE(frame.getReceptor().getReply(600).get() != NULL);
+ }
+ EXPECT_EQUAL(servers.size(), lst.size());
+ for (uint32_t i = 0; i < servers.size(); ++i) {
+ delete servers[i];
+ }
+}
+
+void
+Test::requireThatExternPolicyMergesOneReplyAsProtocol()
+{
+ TestFrame frame(_repo);
+ frame.setMessage(newPutDocumentMessage("doc:scheme:"));
+ mbus::Slobrok slobrok;
+ mbus::TestServer server(mbus::Identity("docproc/cluster.default/0"),
+ mbus::RoutingSpec(), slobrok, "",
+ mbus::IProtocol::SP(new DocumentProtocol(_loadTypes, _repo)));
+ server.net.registerSession("chain.default");
+ setupExternPolicy(frame, slobrok, "docproc/cluster.default/0/chain.default", 1);
+ EXPECT_TRUE(frame.testMergeOneReply(server.net.getConnectionSpec() + "/chain.default"));
+}
+
+mbus::Message::UP
+Test::newPutDocumentMessage(const string &documentId)
+{
+ Document::SP doc(new Document(*_docType, DocumentId(documentId)));
+ return mbus::Message::UP(new PutDocumentMessage(doc));
+}
+
+void
+Test::setupExternPolicy(TestFrame &frame, mbus::Slobrok &slobrok, const string &pattern,
+ int32_t numEntries)
+{
+ string param = vespalib::make_string("tcp/localhost:%d;%s",
+ slobrok.port(), pattern.c_str());
+ frame.setHop(mbus::HopSpec("test", vespalib::make_string("[Extern:%s]", param.c_str())));
+ mbus::MessageBus &mbus = frame.getMessageBus();
+ const mbus::HopBlueprint *hop = mbus.getRoutingTable(DocumentProtocol::NAME)->getHop("test");
+ const mbus::PolicyDirective dir = static_cast<mbus::PolicyDirective&>(*hop->getDirective(0));
+ ExternPolicy &policy = static_cast<ExternPolicy&>(*mbus.getRoutingPolicy(
+ DocumentProtocol::NAME,
+ dir.getName(),
+ dir.getParam()));
+ assertMirrorReady(policy.getMirror());
+ if (numEntries >= 0) {
+ assertMirrorContains(policy.getMirror(), pattern, numEntries);
+ }
+}
+
+void
+Test::assertMirrorReady(const slobrok::api::MirrorAPI &mirror)
+{
+ for (uint32_t i = 0; i < 6000; ++i) {
+ if (mirror.ready()) {
+ return;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ ASSERT_TRUE(false);
+}
+
+void
+Test::assertMirrorContains(const slobrok::api::MirrorAPI &mirror, const string &pattern,
+ uint32_t numEntries)
+{
+ for (uint32_t i = 0; i < 6000; ++i) {
+ if (mirror.lookup(pattern).size() == numEntries) {
+ return;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ ASSERT_TRUE(false);
+}
+
+void
+Test::testExternSend()
+{
+ // Setup local source node.
+ mbus::Slobrok local;
+ mbus::TestServer src(mbus::Identity("src"), mbus::RoutingSpec(), local, "",
+ mbus::IProtocol::SP(
+ new DocumentProtocol(_loadTypes, _repo)));
+ mbus::Receptor sr;
+ mbus::SourceSession::UP ss = src.mb.createSourceSession(sr, mbus::SourceSessionParams().setTimeout(60));
+
+ mbus::Slobrok slobrok;
+ mbus::TestServer itr(mbus::Identity("itr"), mbus::RoutingSpec()
+ .addTable(mbus::RoutingTableSpec(DocumentProtocol::NAME)
+ .addRoute(mbus::RouteSpec("default").addHop("dst"))
+ .addHop(mbus::HopSpec("dst", "dst/session"))),
+ slobrok, "", mbus::IProtocol::SP(
+ new DocumentProtocol(_loadTypes, _repo)));
+ mbus::Receptor ir;
+ mbus::IntermediateSession::UP is = itr.mb.createIntermediateSession("session", true, ir, ir);
+
+ mbus::TestServer dst(mbus::Identity("dst"), mbus::RoutingSpec(), slobrok, "",
+ mbus::IProtocol::SP(new DocumentProtocol(_loadTypes, _repo)));
+ mbus::Receptor dr;
+ mbus::DestinationSession::UP ds = dst.mb.createDestinationSession("session", true, dr);
+
+ // Send message from local node to remote cluster and resolve route there.
+ mbus::Message::UP msg(new GetDocumentMessage(document::DocumentId("doc:scheme:"), 0));
+ msg->getTrace().setLevel(9);
+ msg->setRoute(mbus::Route::parse(vespalib::make_string("[Extern:tcp/localhost:%d;itr/session] default", slobrok.port())));
+
+ ASSERT_TRUE(ss->send(std::move(msg)).isAccepted());
+ ASSERT_TRUE((msg = ir.getMessage(600)).get() != NULL);
+ is->forward(std::move(msg));
+ ASSERT_TRUE((msg = dr.getMessage(600)).get() != NULL);
+ ds->acknowledge(std::move(msg));
+ mbus::Reply::UP reply = ir.getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ is->forward(std::move(reply));
+ ASSERT_TRUE((reply = sr.getReply(600)).get() != NULL);
+
+ fprintf(stderr, "%s", reply->getTrace().toString().c_str());
+}
+
+void
+Test::testExternMultipleSlobroks()
+{
+ mbus::Slobrok local;
+ mbus::TestServer src(mbus::Identity("src"), mbus::RoutingSpec(), local, "",
+ mbus::IProtocol::SP(new DocumentProtocol(_loadTypes, _repo)));
+ mbus::Receptor sr;
+ mbus::SourceSession::UP ss = src.mb.createSourceSession(sr, mbus::SourceSessionParams().setTimeout(60));
+
+ string spec;
+ mbus::Receptor dr;
+ {
+ mbus::Slobrok ext;
+ spec.append(vespalib::make_string("tcp/localhost:%d", ext.port()));
+
+ mbus::TestServer dst(mbus::Identity("dst"), mbus::RoutingSpec(), ext, "",
+ mbus::IProtocol::SP(new DocumentProtocol(_loadTypes, _repo)));
+ mbus::DestinationSession::UP ds = dst.mb.createDestinationSession("session", true, dr);
+
+ mbus::Message::UP msg(new GetDocumentMessage(document::DocumentId("doc:scheme:"), 0));
+ msg->setRoute(mbus::Route::parse(vespalib::make_string("[Extern:%s;dst/session]", spec.c_str())));
+ ASSERT_TRUE(ss->send(std::move(msg)).isAccepted());
+ ASSERT_TRUE((msg = dr.getMessage(600)).get() != NULL);
+ ds->acknowledge(std::move(msg));
+ mbus::Reply::UP reply = sr.getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ }
+ {
+ mbus::Slobrok ext;
+ spec.append(vespalib::make_string(",tcp/localhost:%d", ext.port()));
+
+ mbus::TestServer dst(mbus::Identity("dst"), mbus::RoutingSpec(), ext, "",
+ mbus::IProtocol::SP(new DocumentProtocol(_loadTypes, _repo)));
+ mbus::DestinationSession::UP ds = dst.mb.createDestinationSession("session", true, dr);
+
+ mbus::Message::UP msg(new GetDocumentMessage(document::DocumentId("doc:scheme:"), 0));
+ msg->setRoute(mbus::Route::parse(vespalib::make_string("[Extern:%s;dst/session]", spec.c_str())));
+ ASSERT_TRUE(ss->send(std::move(msg)).isAccepted());
+ ASSERT_TRUE((msg = dr.getMessage(600)).get() != NULL);
+ ds->acknowledge(std::move(msg));
+ mbus::Reply::UP reply = sr.getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ }
+}
+
+void
+Test::testLocalService()
+{
+ // Prepare message.
+ TestFrame frame(_repo, "docproc/cluster.default");
+ frame.setMessage(mbus::Message::UP(new PutDocumentMessage(Document::SP(
+ new Document(*_docType,
+ DocumentId("doc:scheme:"))))));
+
+ // Test select with proper address.
+ for (uint32_t i = 0; i < 10; ++i) {
+ frame.getNetwork().registerSession(vespalib::make_string("%d/chain.default", i));
+ }
+ ASSERT_TRUE(frame.waitSlobrok("docproc/cluster.default/*/chain.default", 10));
+ frame.setHop(mbus::HopSpec("test", "docproc/cluster.default/[LocalService]/chain.default"));
+
+ std::set<string> lst;
+ for (uint32_t i = 0; i < 10; ++i) {
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 1));
+ lst.insert(leaf[0]->getRoute().toString());
+
+ leaf[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ ASSERT_TRUE(frame.getReceptor().getReply(600).get() != NULL);
+ }
+ EXPECT_EQUAL(10u, lst.size());
+
+ // Test select with broken address.
+ lst.clear();
+ frame.setHop(mbus::HopSpec("test", "docproc/cluster.default/[LocalService:broken]/chain.default"));
+ for (uint32_t i = 0; i < 10; ++i) {
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 1));
+ lst.insert(leaf[0]->getRoute().toString());
+
+ leaf[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ ASSERT_TRUE(frame.getReceptor().getReply(600).get() != NULL);
+ }
+ EXPECT_EQUAL(1u, lst.size());
+ EXPECT_EQUAL("docproc/cluster.default/*/chain.default", *lst.begin());
+
+ // Test merge behavior.
+ frame.setHop(mbus::HopSpec("test", "[LocalService]"));
+ EXPECT_TRUE(frame.testMergeOneReply("*"));
+}
+
+void
+Test::testLocalServiceCache()
+{
+ TestFrame fooFrame(_repo, "docproc/cluster.default");
+ mbus::HopSpec fooHop("foo", "docproc/cluster.default/[LocalService]/chain.foo");
+ fooFrame.setMessage(mbus::Message::UP(new GetDocumentMessage(document::DocumentId("doc:scheme:foo"))));
+ fooFrame.setHop(fooHop);
+
+ TestFrame barFrame(fooFrame);
+ mbus::HopSpec barHop("test", "docproc/cluster.default/[LocalService]/chain.bar");
+ barFrame.setMessage(mbus::Message::UP(new GetDocumentMessage(document::DocumentId("doc:scheme:bar"))));
+ barFrame.setHop(barHop);
+
+ fooFrame.getMessageBus().setupRouting(
+ mbus::RoutingSpec().addTable(mbus::RoutingTableSpec(DocumentProtocol::NAME)
+ .addHop(fooHop)
+ .addHop(barHop)));
+
+ fooFrame.getNetwork().registerSession("0/chain.foo");
+ fooFrame.getNetwork().registerSession("0/chain.bar");
+ ASSERT_TRUE(fooFrame.waitSlobrok("docproc/cluster.default/0/*", 2));
+
+ std::vector<mbus::RoutingNode*> fooSelected;
+ ASSERT_TRUE(fooFrame.select(fooSelected, 1));
+ EXPECT_EQUAL("docproc/cluster.default/0/chain.foo", fooSelected[0]->getRoute().getHop(0).toString());
+
+ std::vector<mbus::RoutingNode*> barSelected;
+ ASSERT_TRUE(barFrame.select(barSelected, 1));
+ EXPECT_EQUAL("docproc/cluster.default/0/chain.bar", barSelected[0]->getRoute().getHop(0).toString());
+
+ barSelected[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ fooSelected[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+
+ ASSERT_TRUE(barFrame.getReceptor().getReply(600).get() != NULL);
+ ASSERT_TRUE(fooFrame.getReceptor().getReply(600).get() != NULL);
+}
+
+void
+Test::testRoundRobin()
+{
+ // Prepare message.
+ TestFrame frame(_repo, "docproc/cluster.default");
+ frame.setMessage(mbus::Message::UP(new PutDocumentMessage(Document::SP(
+ new Document(*_docType,
+ DocumentId("doc:scheme:"))))));
+
+ // Test select with proper address.
+ for (uint32_t i = 0; i < 10; ++i) {
+ frame.getNetwork().registerSession(vespalib::make_string("%d/chain.default", i));
+ }
+ ASSERT_TRUE(frame.waitSlobrok("docproc/cluster.default/*/chain.default", 10));
+ frame.setHop(mbus::HopSpec("test", "[RoundRobin]")
+ .addRecipient("docproc/cluster.default/3/chain.default")
+ .addRecipient("docproc/cluster.default/6/chain.default")
+ .addRecipient("docproc/cluster.default/9/chain.default"));
+ EXPECT_TRUE(trySelect(frame, 32, StringList()
+ .add("docproc/cluster.default/3/chain.default")
+ .add("docproc/cluster.default/6/chain.default")
+ .add("docproc/cluster.default/9/chain.default")));
+ frame.getNetwork().unregisterSession("6/chain.default");
+ ASSERT_TRUE(frame.waitSlobrok("docproc/cluster.default/*/chain.default", 9));
+ EXPECT_TRUE(trySelect(frame, 32, StringList()
+ .add("docproc/cluster.default/3/chain.default")
+ .add("docproc/cluster.default/9/chain.default")));
+ frame.getNetwork().unregisterSession("3/chain.default");
+ ASSERT_TRUE(frame.waitSlobrok("docproc/cluster.default/*/chain.default", 8));
+ EXPECT_TRUE(trySelect(frame, 32, StringList()
+ .add("docproc/cluster.default/9/chain.default")));
+ frame.getNetwork().unregisterSession("9/chain.default");
+ ASSERT_TRUE(frame.waitSlobrok("docproc/cluster.default/*/chain.default", 7));
+ EXPECT_TRUE(trySelect(frame, 32, StringList()));
+
+ // Test merge behavior.
+ frame.setHop(mbus::HopSpec("test", "[RoundRobin]").addRecipient("docproc/cluster.default/0/chain.default"));
+ EXPECT_TRUE(frame.testMergeOneReply("docproc/cluster.default/0/chain.default"));
+}
+
+void
+Test::testRoundRobinCache()
+{
+ TestFrame fooFrame(_repo, "docproc/cluster.default");
+ mbus::HopSpec fooHop("foo", "[RoundRobin]");
+ fooHop.addRecipient("docproc/cluster.default/0/chain.foo");
+ fooFrame.setMessage(mbus::Message::UP(new GetDocumentMessage(document::DocumentId("doc:scheme:foo"))));
+ fooFrame.setHop(fooHop);
+
+ TestFrame barFrame(fooFrame);
+ mbus::HopSpec barHop("bar", "[RoundRobin]");
+ barHop.addRecipient("docproc/cluster.default/0/chain.bar");
+ barFrame.setMessage(mbus::Message::UP(new GetDocumentMessage(document::DocumentId("doc:scheme:bar"))));
+ barFrame.setHop(barHop);
+
+ fooFrame.getMessageBus().setupRouting(
+ mbus::RoutingSpec().addTable(mbus::RoutingTableSpec(DocumentProtocol::NAME)
+ .addHop(fooHop)
+ .addHop(barHop)));
+
+ fooFrame.getNetwork().registerSession("0/chain.foo");
+ fooFrame.getNetwork().registerSession("0/chain.bar");
+ ASSERT_TRUE(fooFrame.waitSlobrok("docproc/cluster.default/0/*", 2));
+
+ std::vector<mbus::RoutingNode*> fooSelected;
+ ASSERT_TRUE(fooFrame.select(fooSelected, 1));
+ EXPECT_EQUAL("docproc/cluster.default/0/chain.foo", fooSelected[0]->getRoute().getHop(0).toString());
+
+ std::vector<mbus::RoutingNode*> barSelected;
+ ASSERT_TRUE(barFrame.select(barSelected, 1));
+ EXPECT_EQUAL("docproc/cluster.default/0/chain.bar", barSelected[0]->getRoute().getHop(0).toString());
+
+ barSelected[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ fooSelected[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+
+ ASSERT_TRUE(barFrame.getReceptor().getReply(600).get() != NULL);
+ ASSERT_TRUE(fooFrame.getReceptor().getReply(600).get() != NULL);
+}
+
+void
+Test::testSearchRow()
+{
+ TestFrame frame(_repo);
+ frame.setMessage(mbus::Message::UP(new PutDocumentMessage(Document::SP(
+ new Document(*_docType,
+ DocumentId("doc:scheme:"))))));
+ frame.setHop(mbus::HopSpec("test", "[SearchRow]")
+ .addRecipient("foo"));
+ EXPECT_TRUE(frame.testMergeOneReply("foo"));
+ frame.setHop(mbus::HopSpec("test", "[SearchRow]")
+ .addRecipient("foo")
+ .addRecipient("bar"));
+ EXPECT_TRUE(frame.testMergeTwoReplies("foo", "bar"));
+
+ frame.setHop(mbus::HopSpec("test", "[SearchRow:1]")
+ .addRecipient("foo"));
+ TestFrame::ReplyMap replies;
+ replies["foo"] = mbus::ErrorCode::SERVICE_OOS;
+ EXPECT_TRUE(frame.testMergeError(replies, UIntList().add(mbus::ErrorCode::SERVICE_OOS)));
+
+ frame.setHop(mbus::HopSpec("test", "[SearchRow:1]")
+ .addRecipient("foo")
+ .addRecipient("bar"));
+ replies["foo"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["bar"] = mbus::ErrorCode::NONE;
+ EXPECT_TRUE(frame.testMergeOk(replies, StringList().add("bar")));
+
+ replies["foo"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["bar"] = mbus::ErrorCode::SERVICE_OOS;
+ EXPECT_TRUE(frame.testMergeError(replies, UIntList()
+ .add(mbus::ErrorCode::SERVICE_OOS)
+ .add(mbus::ErrorCode::SERVICE_OOS)));
+
+ frame.setHop(mbus::HopSpec("test", "[SearchRow:1]")
+ .addRecipient("foo")
+ .addRecipient("bar")
+ .addRecipient("baz"));
+ replies["foo"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["bar"] = mbus::ErrorCode::NONE;
+ replies["baz"] = mbus::ErrorCode::NONE;
+ EXPECT_TRUE(frame.testMergeOk(replies, StringList().add("bar").add("baz")));
+
+ replies["foo"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["bar"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["baz"] = mbus::ErrorCode::NONE;
+ EXPECT_TRUE(frame.testMergeOk(replies, StringList().add("baz")));
+
+ replies["foo"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["bar"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["baz"] = mbus::ErrorCode::SERVICE_OOS;
+ EXPECT_TRUE(frame.testMergeError(replies, UIntList()
+ .add(mbus::ErrorCode::SERVICE_OOS)
+ .add(mbus::ErrorCode::SERVICE_OOS)
+ .add(mbus::ErrorCode::SERVICE_OOS)));
+
+ frame.setHop(mbus::HopSpec("test", "[SearchRow:2]")
+ .addRecipient("foo")
+ .addRecipient("bar")
+ .addRecipient("baz"));
+ replies["foo"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["bar"] = mbus::ErrorCode::NONE;
+ replies["baz"] = mbus::ErrorCode::NONE;
+ EXPECT_TRUE(frame.testMergeOk(replies, StringList().add("bar").add("baz")));
+
+ replies["foo"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["bar"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["baz"] = mbus::ErrorCode::NONE;
+ EXPECT_TRUE(frame.testMergeError(replies, UIntList()
+ .add(mbus::ErrorCode::SERVICE_OOS)
+ .add(mbus::ErrorCode::SERVICE_OOS)));
+
+ replies["foo"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["bar"] = mbus::ErrorCode::SERVICE_OOS;
+ replies["baz"] = mbus::ErrorCode::SERVICE_OOS;
+ EXPECT_TRUE(frame.testMergeError(replies, UIntList()
+ .add(mbus::ErrorCode::SERVICE_OOS)
+ .add(mbus::ErrorCode::SERVICE_OOS)
+ .add(mbus::ErrorCode::SERVICE_OOS)));
+}
+
+void
+Test::testSearchRowMerge()
+{
+ TestFrame frame(_repo);
+ frame.setHop(mbus::HopSpec("test", "[SearchRow]")
+ .addRecipient("foo"));
+ tryWasFound(frame, 1, 0x0, false);
+ tryWasFound(frame, 1, 0x1, true);
+
+ frame.setHop(mbus::HopSpec("test", "[SearchRow]")
+ .addRecipient("foo")
+ .addRecipient("bar"));
+ tryWasFound(frame, 2, 0x0, false);
+ tryWasFound(frame, 2, 0x1, true);
+ tryWasFound(frame, 2, 0x2, true);
+ tryWasFound(frame, 2, 0x3, true);
+
+ frame.setHop(mbus::HopSpec("test", "[SearchRow]")
+ .addRecipient("foo")
+ .addRecipient("bar")
+ .addRecipient("baz"));
+ tryWasFound(frame, 3, 0x0, false);
+ tryWasFound(frame, 3, 0x1, true);
+ tryWasFound(frame, 3, 0x2, true);
+ tryWasFound(frame, 3, 0x3, true);
+ tryWasFound(frame, 3, 0x4, true);
+ tryWasFound(frame, 3, 0x5, true);
+ tryWasFound(frame, 3, 0x6, true);
+ tryWasFound(frame, 3, 0x7, true);
+}
+
+void
+Test::tryWasFound(TestFrame &frame, uint32_t expectedRecipients,
+ uint32_t foundMask, bool expectedFound)
+{
+ {
+ frame.setMessage(mbus::Message::UP(new RemoveDocumentMessage(DocumentId("doc:scheme:69"))));
+ std::vector<mbus::RoutingNode*> selected;
+ EXPECT_TRUE(frame.select(selected, expectedRecipients));
+ for (uint32_t i = 0, len = selected.size(); i < len; ++i) {
+ mbus::Reply::UP reply(new RemoveDocumentReply());
+ static_cast<RemoveDocumentReply&>(*reply).setWasFound((1 << i) & foundMask);
+ selected[i]->handleReply(std::move(reply));
+ }
+ mbus::Reply::UP reply = frame.getReceptor().getReply(600);
+ EXPECT_TRUE(reply.get() != NULL);
+ EXPECT_EQUAL((uint32_t)DocumentProtocol::REPLY_REMOVEDOCUMENT, reply->getType());
+ EXPECT_EQUAL(expectedFound, static_cast<RemoveDocumentReply&>(*reply).wasFound());
+ }
+ {
+ DocumentUpdate::SP upd(new DocumentUpdate(*_docType, DocumentId("doc:scheme:")));
+ frame.setMessage(mbus::Message::UP(new UpdateDocumentMessage(upd)));
+ std::vector<mbus::RoutingNode*> selected;
+ EXPECT_TRUE(frame.select(selected, expectedRecipients));
+ for (uint32_t i = 0, len = selected.size(); i < len; ++i) {
+ mbus::Reply::UP reply(new UpdateDocumentReply());
+ static_cast<UpdateDocumentReply&>(*reply).setWasFound((1 << i) & foundMask);
+ selected[i]->handleReply(std::move(reply));
+ }
+ mbus::Reply::UP reply = frame.getReceptor().getReply(600);
+ EXPECT_TRUE(reply.get() != NULL);
+ EXPECT_EQUAL((uint32_t)DocumentProtocol::REPLY_UPDATEDOCUMENT, reply->getType());
+ EXPECT_EQUAL(expectedFound, static_cast<UpdateDocumentReply&>(*reply).wasFound());
+ }
+}
+
+void
+Test::multipleGetRepliesAreMergedToFoundDocument()
+{
+ TestFrame frame(_repo);
+ frame.setHop(mbus::HopSpec("test", "[DocumentRouteSelector:raw:"
+ "route[2]\n"
+ "route[0].name \"foo\"\n"
+ "route[0].selector \"testdoc\"\n"
+ "route[0].feed \"myfeed\"\n"
+ "route[1].name \"bar\"\n"
+ "route[1].selector \"other\"\n"
+ "route[1].feed \"myfeed\"\n]")
+ .addRecipient("foo")
+ .addRecipient("bar"));
+ frame.setMessage(mbus::Message::UP(new GetDocumentMessage(DocumentId("doc:scheme:yarn"))));
+ std::vector<mbus::RoutingNode*> selected;
+ EXPECT_TRUE(frame.select(selected, 2));
+ for (uint32_t i = 0, len = selected.size(); i < len; ++i) {
+ document::Document::SP doc;
+ if (i == 0) {
+ doc.reset(new Document(*_docType, DocumentId("doc:scheme:yarn")));
+ doc->setLastModified(123456ULL);
+ }
+ mbus::Reply::UP reply(new GetDocumentReply(doc));
+ selected[i]->handleReply(std::move(reply));
+ }
+ mbus::Reply::UP reply = frame.getReceptor().getReply(600);
+ EXPECT_TRUE(reply.get() != NULL);
+ EXPECT_EQUAL(static_cast<uint32_t>(DocumentProtocol::REPLY_GETDOCUMENT),
+ reply->getType());
+ EXPECT_EQUAL(123456ULL, static_cast<GetDocumentReply&>(*reply).getLastModified());
+}
+
+void
+Test::testSearchColumn()
+{
+ TestFrame frame(_repo);
+ frame.setHop(mbus::HopSpec("test", "[SearchColumn]")
+ .addRecipient("c0")
+ .addRecipient("c1")
+ .addRecipient("c2")
+ .addRecipient("c3"));
+
+ // Test hash distribution.
+ EXPECT_TRUE(tryDistribution(frame, "doc:ns:3", "c0"));
+ EXPECT_TRUE(tryDistribution(frame, "doc:ns:18", "c1"));
+ EXPECT_TRUE(tryDistribution(frame, "doc:ns:0", "c2"));
+ EXPECT_TRUE(tryDistribution(frame, "doc:ns:4", "c3"));
+
+ EXPECT_TRUE(tryDistribution(frame, "userdoc:ns:49152:0", "c0"));
+ EXPECT_TRUE(tryDistribution(frame, "userdoc:ns:49152:1", "c0"));
+ EXPECT_TRUE(tryDistribution(frame, "userdoc:ns:16384:2", "c1"));
+ EXPECT_TRUE(tryDistribution(frame, "userdoc:ns:16384:3", "c1"));
+ EXPECT_TRUE(tryDistribution(frame, "userdoc:ns:5461:4", "c2"));
+ EXPECT_TRUE(tryDistribution(frame, "userdoc:ns:5461:5", "c2"));
+ EXPECT_TRUE(tryDistribution(frame, "userdoc:ns:0:6", "c3"));
+ EXPECT_TRUE(tryDistribution(frame, "userdoc:ns:0:7", "c3"));
+
+ EXPECT_TRUE(tryDistribution(frame, "groupdoc:ns:0:0", "c0"));
+ EXPECT_TRUE(tryDistribution(frame, "groupdoc:ns:0:1", "c0"));
+ EXPECT_TRUE(tryDistribution(frame, "groupdoc:ns:4:2", "c1"));
+ EXPECT_TRUE(tryDistribution(frame, "groupdoc:ns:4:3", "c1"));
+ EXPECT_TRUE(tryDistribution(frame, "groupdoc:ns:2:4", "c2"));
+ EXPECT_TRUE(tryDistribution(frame, "groupdoc:ns:2:5", "c2"));
+ EXPECT_TRUE(tryDistribution(frame, "groupdoc:ns:7:6", "c3"));
+ EXPECT_TRUE(tryDistribution(frame, "groupdoc:ns:7:7", "c3"));
+
+ // Test routing based on message type.
+ mbus::Message::UP put(new PutDocumentMessage(Document::SP(
+ new Document(*_docType,
+ DocumentId("doc:scheme:")))));
+}
+
+bool
+Test::tryDistribution(TestFrame &frame, const string &id, const string &expected)
+{
+ Document::SP doc(new Document(*_docType, DocumentId(id)));
+ mbus::Message::UP msg(new PutDocumentMessage(doc));
+ frame.setMessage(std::move(msg));
+ return frame.testSelect(StringList().add(expected));
+}
+
+void
+Test::testDocumentRouteSelector()
+{
+ // Test policy with usage safeguard.
+ string okConfig = "raw:route[0]\n";
+ string errConfig = "raw:"
+ "route[1]\n"
+ "route[0].name \"foo\"\n"
+ "route[0].selector \"foo bar\"\n"
+ "route[0].feed \"baz\"\n";
+ {
+ DocumentProtocol protocol(_loadTypes, _repo, okConfig);
+ EXPECT_TRUE(dynamic_cast<DocumentRouteSelectorPolicy*>(protocol.createPolicy("DocumentRouteSelector", "").get()) != NULL);
+ EXPECT_TRUE(dynamic_cast<ErrorPolicy*>(protocol.createPolicy("DocumentRouteSelector", errConfig).get()) != NULL);
+ }
+ {
+ DocumentProtocol protocol(_loadTypes, _repo, errConfig);
+ EXPECT_TRUE(dynamic_cast<ErrorPolicy*>(protocol.createPolicy("DocumentRouteSelector", "").get()) != NULL);
+ EXPECT_TRUE(dynamic_cast<DocumentRouteSelectorPolicy*>(protocol.createPolicy("DocumentRouteSelector", okConfig).get()) != NULL);
+ }
+
+ // Test policy with proper config.
+ TestFrame frame(_repo);
+ frame.setHop(mbus::HopSpec("test", "[DocumentRouteSelector:raw:"
+ "route[2]\n"
+ "route[0].name \"foo\"\n"
+ "route[0].selector \"testdoc\"\n"
+ "route[0].feed \"myfeed\"\n"
+ "route[1].name \"bar\"\n"
+ "route[1].selector \"other\"\n"
+ "route[1].feed \"myfeed\"\n]")
+ .addRecipient("foo")
+ .addRecipient("bar"));
+
+ frame.setMessage(mbus::Message::UP(new GetDocumentMessage(document::DocumentId("doc:scheme:"), 0)));
+ EXPECT_TRUE(frame.testSelect(StringList().add("foo").add("bar")));
+
+ mbus::Message::UP put(new PutDocumentMessage(Document::SP(
+ new Document(*_docType,
+ DocumentId("doc:scheme:")))));
+ frame.setMessage(std::move(put));
+ EXPECT_TRUE(frame.testSelect( StringList().add("foo")));
+
+ {
+ vdslib::OperationList opList;
+
+ document::DocumentId id("doc:scheme:");
+ Document::UP doc(new Document(*_docType, id));
+ opList.addPut(std::move(doc));
+
+ document::BucketIdFactory factory;
+ put = frame.setMessage(MultiOperationMessage::create(_repo, factory.getBucketId(id), opList));
+ EXPECT_TRUE(frame.testSelect(StringList().add("foo")));
+ }
+
+ {
+ vdslib::OperationList opList;
+ document::DocumentId id("doc:scheme:");
+ Document::UP doc(new Document(*_repo->getDocumentType("other"), id));
+ opList.addPut(std::move(doc));
+
+ document::BucketIdFactory factory;
+ put = frame.setMessage(MultiOperationMessage::create(_repo,
+ factory.getBucketId(id), opList));
+ EXPECT_TRUE(frame.testSelect(StringList().add("bar")));
+ }
+
+ frame.setMessage(mbus::Message::UP(new RemoveDocumentMessage(document::DocumentId("doc:scheme:"))));
+ EXPECT_TRUE(frame.testSelect(StringList().add("foo").add("bar")));
+
+ frame.setMessage(mbus::Message::UP(new UpdateDocumentMessage(
+ document::DocumentUpdate::SP(
+ new document::DocumentUpdate(
+ *_docType,
+ DocumentId("doc:scheme:"))))));
+ EXPECT_TRUE(frame.testSelect(StringList().add("foo")));
+
+ frame.setMessage(std::move(put));
+ EXPECT_TRUE(frame.testMergeOneReply("foo"));
+}
+
+void
+Test::testDocumentRouteSelectorIgnore()
+{
+ TestFrame frame(_repo);
+ frame.setHop(mbus::HopSpec("test", "[DocumentRouteSelector:raw:"
+ "route[1]\n"
+ "route[0].name \"docproc/cluster.foo\"\n"
+ "route[0].selector \"testdoc and testdoc.stringfield == 'foo'\"\n"
+ "route[0].feed \"myfeed\"\n]")
+ .addRecipient("docproc/cluster.foo"));
+
+ frame.setMessage(mbus::Message::UP(new PutDocumentMessage(
+ document::Document::SP(
+ new document::Document(*_docType,
+ DocumentId("id:yarn:testdoc:n=1234:fluff"))))));
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 0));
+ mbus::Reply::UP reply = frame.getReceptor().getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ EXPECT_EQUAL(uint32_t(DocumentProtocol::REPLY_DOCUMENTIGNORED), reply->getType());
+ EXPECT_EQUAL(0u, reply->getNumErrors());
+
+ frame.setMessage(mbus::Message::UP(new UpdateDocumentMessage(
+ document::DocumentUpdate::SP(
+ new document::DocumentUpdate(
+ *_docType,
+ DocumentId("doc:scheme:"))))));
+ EXPECT_TRUE(frame.testSelect(StringList().add("docproc/cluster.foo")));
+}
+
+namespace {
+ string getDefaultDistributionConfig(
+ uint16_t redundancy = 2, uint16_t nodeCount = 10,
+ vespa::config::content::StorDistributionConfig::DiskDistribution distr
+ = vespa::config::content::StorDistributionConfig::MODULO_BID)
+ {
+ std::ostringstream ost;
+ ost << "raw:redundancy " << redundancy << "\n"
+ << "group[1]\n"
+ << "group[0].index \"invalid\"\n"
+ << "group[0].name \"invalid\"\n"
+ << "group[0].partitions \"*\"\n"
+ << "group[0].nodes[" << nodeCount << "]\n";
+ for (uint16_t i=0; i<nodeCount; ++i) {
+ ost << "group[0].nodes[" << i << "].index " << i << "\n";
+ }
+ ost << "disk_distribution "
+ << vespa::config::content::StorDistributionConfig::getDiskDistributionName(distr)
+ << "\n";
+ return ost.str();
+ }
+}
+
+void Test::testLoadBalancer() {
+ LoadBalancer lb("foo", "");
+
+ MirrorAPI::SpecList entries;
+ entries.push_back(MirrorAPI::Spec("foo/0/default", "tcp/bar:1"));
+ entries.push_back(MirrorAPI::Spec("foo/1/default", "tcp/bar:2"));
+ entries.push_back(MirrorAPI::Spec("foo/2/default", "tcp/bar:3"));
+
+ const std::vector<LoadBalancer::NodeInfo>& nodeInfo = lb.getNodeInfo();
+
+ for (int i = 0; i < 99; i++) {
+ std::pair<string, int> recipient = lb.getRecipient(entries);
+ EXPECT_EQUAL((i % 3), recipient.second);
+ }
+
+ // Simulate that one node is overloaded. It returns busy twice as often as the others.
+ for (int i = 0; i < 100; i++) {
+ lb.received(0, true);
+ lb.received(0, false);
+ lb.received(0, false);
+ lb.received(2, true);
+ lb.received(2, false);
+ lb.received(2, false);
+ lb.received(1, true);
+ lb.received(1, true);
+ lb.received(1, false);
+ }
+
+ EXPECT_EQUAL(421, (int)(100 * nodeInfo[0].weight / nodeInfo[1].weight));
+ EXPECT_EQUAL(421, (int)(100 * nodeInfo[2].weight / nodeInfo[1].weight));
+
+ EXPECT_EQUAL(0 , lb.getRecipient(entries).second);
+ EXPECT_EQUAL(0 , lb.getRecipient(entries).second);
+ EXPECT_EQUAL(1 , lb.getRecipient(entries).second);
+ EXPECT_EQUAL(2 , lb.getRecipient(entries).second);
+ EXPECT_EQUAL(2 , lb.getRecipient(entries).second);
+ EXPECT_EQUAL(2 , lb.getRecipient(entries).second);
+ EXPECT_EQUAL(2 , lb.getRecipient(entries).second);
+ EXPECT_EQUAL(0 , lb.getRecipient(entries).second);
+ EXPECT_EQUAL(0 , lb.getRecipient(entries).second);
+ EXPECT_EQUAL(0 , lb.getRecipient(entries).second);
+}
+
+void
+Test::requireThatStoragePolicyWithIllegalParamIsAnErrorPolicy()
+{
+ EXPECT_TRUE(isErrorPolicy("Storage", ""));
+ EXPECT_TRUE(isErrorPolicy("Storage", "config=foo;slobroks=foo"));
+ EXPECT_TRUE(isErrorPolicy("Storage", "slobroks=foo"));
+}
+
+void
+Test::requireThatStoragePolicyIsRandomWithoutState()
+{
+ TestFrame frame(_repo);
+ frame.setMessage(newPutDocumentMessage("doc:scheme:"));
+
+ mbus::Slobrok slobrok;
+ std::vector<mbus::TestServer*> servers;
+ for (uint32_t i = 0; i < 5; ++i) {
+ mbus::TestServer *srv = new mbus::TestServer(
+ mbus::Identity(vespalib::make_string("storage/cluster.mycluster/distributor/%d", i)),
+ mbus::RoutingSpec(), slobrok, "",
+ mbus::IProtocol::SP(new DocumentProtocol(_loadTypes, _repo)));
+ servers.push_back(srv);
+ srv->net.registerSession("default");
+ }
+ string param = vespalib::make_string(
+ "cluster=mycluster;slobroks=tcp/localhost:%d;clusterconfigid=%s;syncinit",
+ slobrok.port(), getDefaultDistributionConfig(2, 5).c_str());
+ StoragePolicy &policy = setupStoragePolicy(
+ frame, param,
+ "storage/cluster.mycluster/distributor/*/default", 5);
+ ASSERT_TRUE(policy.getSystemState() == NULL);
+
+ std::set<string> lst;
+ for (uint32_t i = 0; i < 666; i++) {
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 1));
+ lst.insert(leaf[0]->getRoute().toString());
+ leaf[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ }
+ EXPECT_EQUAL(servers.size(), lst.size());
+ for (uint32_t i = 0; i < servers.size(); ++i) {
+ delete servers[i];
+ }
+}
+
+StoragePolicy &
+Test::setupStoragePolicy(TestFrame &frame, const string &param,
+ const string &pattern, int32_t numEntries)
+{
+ frame.setHop(mbus::HopSpec("test", vespalib::make_string("[Storage:%s]", param.c_str())));
+ mbus::MessageBus &mbus = frame.getMessageBus();
+ const mbus::HopBlueprint *hop = mbus.getRoutingTable(DocumentProtocol::NAME)->getHop("test");
+ const mbus::PolicyDirective dir = static_cast<mbus::PolicyDirective&>(*hop->getDirective(0));
+ StoragePolicy &policy = static_cast<StoragePolicy&>(*mbus.getRoutingPolicy(
+ DocumentProtocol::NAME,
+ dir.getName(),
+ dir.getParam()));
+ policy.initSynchronous();
+ assertMirrorReady(*policy.getMirror());
+ if (numEntries >= 0) {
+ assertMirrorContains(*policy.getMirror(), pattern, numEntries);
+ }
+ return policy;
+}
+
+void
+Test::requireThatStoragePolicyIsTargetedWithState()
+{
+ TestFrame frame(_repo);
+ frame.setMessage(newPutDocumentMessage("doc:scheme:"));
+
+ mbus::Slobrok slobrok;
+ std::vector<mbus::TestServer*> servers;
+ for (uint32_t i = 0; i < 5; ++i) {
+ mbus::TestServer *srv = new mbus::TestServer(
+ mbus::Identity(vespalib::make_string("storage/cluster.mycluster/distributor/%d", i)),
+ mbus::RoutingSpec(), slobrok, "",
+ mbus::IProtocol::SP(new DocumentProtocol(_loadTypes, _repo)));
+ servers.push_back(srv);
+ srv->net.registerSession("default");
+ }
+ string param = vespalib::make_string(
+ "cluster=mycluster;slobroks=tcp/localhost:%d;clusterconfigid=%s;syncinit",
+ slobrok.port(), getDefaultDistributionConfig(2, 5).c_str());
+ StoragePolicy &policy = setupStoragePolicy(
+ frame, param,
+ "storage/cluster.mycluster/distributor/*/default", 5);
+ ASSERT_TRUE(policy.getSystemState() == NULL);
+ {
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 1));
+ leaf[0]->handleReply(mbus::Reply::UP(new WrongDistributionReply("distributor:5 storage:5")));
+ ASSERT_TRUE(policy.getSystemState() != NULL);
+ EXPECT_EQUAL(policy.getSystemState()->toString(), "distributor:5 storage:5");
+ }
+ std::set<string> lst;
+ for (int i = 0; i < 666; i++) {
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 1));
+ lst.insert(leaf[0]->getRoute().toString());
+ leaf[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ }
+ EXPECT_EQUAL(1u, lst.size());
+ for (uint32_t i = 0; i < servers.size(); ++i) {
+ delete servers[i];
+ }
+}
+
+void
+Test::requireThatStoragePolicyCombinesSystemAndSlobrokState()
+{
+ TestFrame frame(_repo);
+ frame.setMessage(newPutDocumentMessage("doc:scheme:"));
+
+ mbus::Slobrok slobrok;
+ mbus::TestServer server(mbus::Identity("storage/cluster.mycluster/distributor/0"),
+ mbus::RoutingSpec(), slobrok, "",
+ mbus::IProtocol::SP(new DocumentProtocol(_loadTypes, _repo)));
+ server.net.registerSession("default");
+
+ string param = vespalib::make_string(
+ "cluster=mycluster;slobroks=tcp/localhost:%d;clusterconfigid=%s;syncinit",
+ slobrok.port(), getDefaultDistributionConfig(2, 5).c_str());
+ StoragePolicy &policy = setupStoragePolicy(
+ frame, param,
+ "storage/cluster.mycluster/distributor/*/default", 1);
+ ASSERT_TRUE(policy.getSystemState() == NULL);
+ {
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 1));
+ leaf[0]->handleReply(mbus::Reply::UP(new WrongDistributionReply("distributor:99 storage:99")));
+ ASSERT_TRUE(policy.getSystemState() != NULL);
+ EXPECT_EQUAL(policy.getSystemState()->toString(), "distributor:99 storage:99");
+ }
+ for (int i = 0; i < 666; i++) {
+ ASSERT_TRUE(frame.testSelect(StringList().add(server.net.getConnectionSpec() + "/default")));
+ }
+}
+
+void
+Test::testSubsetService()
+{
+ // Prepare message.
+ TestFrame frame(_repo, "docproc/cluster.default");
+ frame.setMessage(mbus::Message::UP(new PutDocumentMessage(Document::SP(
+ new Document(*_docType,
+ DocumentId("doc:scheme:"))))));
+
+ // Test requerying for adding nodes.
+ frame.setHop(mbus::HopSpec("test", "docproc/cluster.default/[SubsetService:2]/chain.default"));
+ std::set<string> lst;
+ for (uint32_t i = 1; i <= 10; ++i) {
+ frame.getNetwork().registerSession(vespalib::make_string("%d/chain.default", i));
+ ASSERT_TRUE(frame.waitSlobrok("docproc/cluster.default/*/chain.default", i));
+
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 1));
+ lst.insert(leaf[0]->getRoute().toString());
+ leaf[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ ASSERT_TRUE(frame.getReceptor().getReply(600).get() != NULL);
+ }
+ ASSERT_TRUE(lst.size() > 1); // must have requeried
+
+ // Test load balancing.
+ string prev = "";
+ for (uint32_t i = 1; i <= 10; ++i) {
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 1));
+
+ string next = leaf[0]->getRoute().toString();
+ if (prev.empty()) {
+ ASSERT_TRUE(!next.empty());
+ } else {
+ ASSERT_TRUE(prev != next);
+ }
+
+ prev = next;
+ leaf[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ ASSERT_TRUE(frame.getReceptor().getReply(600).get() != NULL);
+ }
+
+ // Test requerying for dropping nodes.
+ lst.clear();
+ for (uint32_t i = 1; i <= 10; ++i) {
+ std::vector<mbus::RoutingNode*> leaf;
+ ASSERT_TRUE(frame.select(leaf, 1));
+ string route = leaf[0]->getRoute().toString();
+ lst.insert(route);
+
+ frame.getNetwork().unregisterSession(route.substr(frame.getIdentity().length() + 1));
+ ASSERT_TRUE(frame.waitSlobrok("docproc/cluster.default/*/chain.default", 10 - i));
+
+ mbus::Reply::UP reply(new mbus::EmptyReply());
+ reply->addError(mbus::Error(mbus::ErrorCode::NO_ADDRESS_FOR_SERVICE, route));
+ leaf[0]->handleReply(std::move(reply));
+ ASSERT_TRUE(frame.getReceptor().getReply(600).get() != NULL);
+ }
+ EXPECT_EQUAL(10u, lst.size());
+
+ // Test merge behavior.
+ frame.setHop(mbus::HopSpec("test", "[SubsetService]"));
+ EXPECT_TRUE(frame.testMergeOneReply("*"));
+}
+
+void
+Test::testSubsetServiceCache()
+{
+ TestFrame fooFrame(_repo, "docproc/cluster.default");
+ mbus::HopSpec fooHop("foo", "docproc/cluster.default/[SubsetService:2]/chain.foo");
+ fooFrame.setMessage(mbus::Message::UP(new GetDocumentMessage(document::DocumentId("doc:scheme:foo"))));
+ fooFrame.setHop(fooHop);
+
+ TestFrame barFrame(fooFrame);
+ mbus::HopSpec barHop("bar", "docproc/cluster.default/[SubsetService:2]/chain.bar");
+ barFrame.setMessage(mbus::Message::UP(new GetDocumentMessage(document::DocumentId("doc:scheme:bar"))));
+ barFrame.setHop(barHop);
+
+ fooFrame.getMessageBus().setupRouting(
+ mbus::RoutingSpec().addTable(mbus::RoutingTableSpec(DocumentProtocol::NAME)
+ .addHop(fooHop)
+ .addHop(barHop)));
+
+ fooFrame.getNetwork().registerSession("0/chain.foo");
+ fooFrame.getNetwork().registerSession("0/chain.bar");
+ ASSERT_TRUE(fooFrame.waitSlobrok("docproc/cluster.default/0/*", 2));
+
+ std::vector<mbus::RoutingNode*> fooSelected;
+ ASSERT_TRUE(fooFrame.select(fooSelected, 1));
+ EXPECT_EQUAL("docproc/cluster.default/0/chain.foo", fooSelected[0]->getRoute().getHop(0).toString());
+
+ std::vector<mbus::RoutingNode*> barSelected;
+ ASSERT_TRUE(barFrame.select(barSelected, 1));
+ EXPECT_EQUAL("docproc/cluster.default/0/chain.bar", barSelected[0]->getRoute().getHop(0).toString());
+
+ barSelected[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ fooSelected[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+
+ ASSERT_TRUE(barFrame.getReceptor().getReply(600).get() != NULL);
+ ASSERT_TRUE(fooFrame.getReceptor().getReply(600).get() != NULL);
+}
+
+bool
+Test::trySelect(TestFrame &frame, uint32_t numSelects, const std::vector<string> &expected) {
+ std::set<string> lst;
+ for (uint32_t i = 0; i < numSelects; ++i) {
+ std::vector<mbus::RoutingNode*> leaf;
+ if (!expected.empty()) {
+ frame.select(leaf, 1);
+ lst.insert(leaf[0]->getRoute().toString());
+ leaf[0]->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ } else {
+ frame.select(leaf, 0);
+ }
+ if(frame.getReceptor().getReply(600).get() == NULL) {
+ LOG(error, "Reply failed to propagate to reply handler.");
+ return false;
+ }
+ }
+ if (expected.size() != lst.size()) {
+ LOG(error, "Expected %d recipients, got %d.", (uint32_t)expected.size(), (uint32_t)lst.size());
+ return false;
+ }
+ std::set<string>::iterator it = lst.begin();
+ for (uint32_t i = 0; i < expected.size(); ++i, ++it) {
+ if (*it != expected[i]) {
+ LOG(error, "Expected '%s', got '%s'.", expected[i].c_str(), it->c_str());
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+Test::isErrorPolicy(const string &name, const string &param)
+{
+ DocumentProtocol protocol(_loadTypes, _repo);
+ mbus::IRoutingPolicy::UP policy = protocol.createPolicy(name, param);
+
+ return policy.get() != NULL && dynamic_cast<ErrorPolicy*>(policy.get()) != NULL;
+}
+
diff --git a/documentapi/src/tests/policies/testframe.cpp b/documentapi/src/tests/policies/testframe.cpp
new file mode 100644
index 00000000000..cb30e5377aa
--- /dev/null
+++ b/documentapi/src/tests/policies/testframe.cpp
@@ -0,0 +1,336 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP(".testframe");
+
+#include "testframe.h"
+#include <vespa/messagebus/emptyreply.h>
+#include <vespa/messagebus/network/rpcnetwork.h>
+#include <vespa/messagebus/sendproxy.h>
+#include <vespa/messagebus/testlib/simplemessage.h>
+#include <vespa/messagebus/testlib/simpleprotocol.h>
+#include <vespa/messagebus/testlib/simplereply.h>
+
+using document::DocumentTypeRepo;
+using namespace documentapi;
+
+class MyServiceAddress : public mbus::IServiceAddress {
+private:
+ string _address;
+
+public:
+ MyServiceAddress(const string &address) :
+ _address(address) {
+ // empty
+ }
+
+ const string &getAddress() {
+ return _address;
+ }
+};
+
+class MyNetwork : public mbus::RPCNetwork {
+private:
+ std::vector<mbus::RoutingNode*> _nodes;
+
+public:
+ MyNetwork(const mbus::RPCNetworkParams &params) :
+ mbus::RPCNetwork(params),
+ _nodes() {
+ // empty
+ }
+
+ bool allocServiceAddress(mbus::RoutingNode &recipient) {
+ string hop = recipient.getRoute().getHop(0).toString();
+ recipient.setServiceAddress(mbus::IServiceAddress::UP(new MyServiceAddress(hop)));
+ return true;
+ }
+
+ void freeServiceAddress(mbus::RoutingNode &recipient) {
+ recipient.setServiceAddress(mbus::IServiceAddress::UP());
+ }
+
+ void send(const mbus::Message &, const std::vector<mbus::RoutingNode*> &nodes) {
+ _nodes.insert(_nodes.begin(), nodes.begin(), nodes.end());
+ }
+
+ void removeNodes(std::vector<mbus::RoutingNode*> &nodes) {
+ nodes.insert(nodes.begin(), _nodes.begin(), _nodes.end());
+ _nodes.clear();
+ }
+};
+
+TestFrame::TestFrame(const DocumentTypeRepo::SP &repo, const string &ident) :
+ _identity(ident),
+ _slobrok(new mbus::Slobrok()),
+ _set(),
+ _net(new MyNetwork(mbus::RPCNetworkParams()
+ .setIdentity(mbus::Identity(ident))
+ .setSlobrokConfig(_slobrok->config()))),
+ _mbus(new mbus::MessageBus(*_net, mbus::MessageBusParams()
+ .addProtocol(mbus::IProtocol::SP(new DocumentProtocol(_set, repo))))),
+ _msg(),
+ _hop(mbus::HopSpec("foo", "bar")),
+ _handler()
+{
+ // empty
+}
+
+TestFrame::TestFrame(TestFrame &frame) :
+ mbus::IReplyHandler(),
+ _identity(frame._identity),
+ _slobrok(frame._slobrok),
+ _net(frame._net),
+ _mbus(frame._mbus),
+ _msg(),
+ _hop(mbus::HopSpec("baz", "cox")),
+ _handler()
+{
+ // empty
+}
+
+TestFrame::~TestFrame()
+{
+ // empty
+}
+
+void
+TestFrame::setHop(const mbus::HopSpec &hop)
+{
+ _hop = hop;
+ _mbus->setupRouting(mbus::RoutingSpec().addTable(mbus::RoutingTableSpec(DocumentProtocol::NAME).addHop(_hop)));
+}
+
+bool
+TestFrame::select(std::vector<mbus::RoutingNode*> &selected, uint32_t numExpected)
+{
+ _msg->setRoute(mbus::Route::parse(_hop.getName()));
+ _msg->pushHandler(*this);
+ mbus::SendProxy &proxy = *(new mbus::SendProxy(*_mbus, *_net, NULL)); // deletes self
+ proxy.handleMessage(std::move(_msg));
+
+ static_cast<MyNetwork&>(*_net).removeNodes(selected);
+ if (selected.size() != numExpected) {
+ LOG(error, "Expected %d recipients, got %d.", numExpected, (uint32_t)selected.size());
+ return false;
+ }
+ return true;
+}
+
+bool
+TestFrame::testSelect(const std::vector<string> &expected)
+{
+ std::vector<mbus::RoutingNode*> selected;
+ if (!select(selected, expected.size())) {
+ LOG(error, "Failed to select recipients.");
+ for (size_t i = 0; i < selected.size(); ++i) {
+ LOG(error, "Selected: %s",
+ selected[i]->getRoute().toString().c_str());
+ }
+ return false;
+ }
+ for (std::vector<mbus::RoutingNode*>::iterator it = selected.begin();
+ it != selected.end(); ++it)
+ {
+ string route = (*it)->getRoute().toString();
+ if (find(expected.begin(), expected.end(), route) == expected.end()) {
+ LOG(error, "Recipient '%s' not selected.", route.c_str());
+ }
+ (*it)->handleReply(mbus::Reply::UP(new mbus::EmptyReply()));
+ }
+ if (_handler.getReply(600).get() == NULL) {
+ LOG(error, "Reply not propagated to handler.");
+ return false;
+ }
+ return true;
+}
+
+bool
+TestFrame::testMergeError(const ReplyMap &replies, const std::vector<uint32_t> &expectedErrors)
+{
+ return testMerge(replies, expectedErrors, StringList());
+}
+
+bool
+TestFrame::testMergeOk(const ReplyMap &replies, const std::vector<string> &allowedValues)
+{
+ return testMerge(replies, UIntList(), allowedValues);
+}
+
+bool
+TestFrame::testMerge(const ReplyMap &replies,
+ const std::vector<uint32_t> &expectedErrors,
+ const std::vector<string> &allowedValues)
+{
+ std::vector<mbus::RoutingNode*> selected;
+ if (!select(selected, replies.size())) {
+ return false;
+ }
+
+ for (std::vector<mbus::RoutingNode*>::iterator it = selected.begin();
+ it != selected.end(); ++it)
+ {
+ string route = (*it)->getRoute().toString();
+ ReplyMap::const_iterator mip = replies.find(route);
+ if (mip == replies.end()) {
+ LOG(error, "Recipient '%s' not expected.", route.c_str());
+ return false;
+ }
+
+ mbus::Reply::UP ret(new mbus::SimpleReply(route));
+ if (mip->second != mbus::ErrorCode::NONE) {
+ ret->addError(mbus::Error(mip->second, route));
+ }
+ (*it)->handleReply(std::move(ret));
+ }
+
+ mbus::Reply::UP reply = _handler.getReply(600);
+ if (reply.get() == NULL) {
+ LOG(error, "Reply not propagated to handler.");
+ return false;
+ }
+ if (!expectedErrors.empty()) {
+ if (expectedErrors.size() != reply->getNumErrors()) {
+ LOG(error, "Expected %d errors, got %d.", (uint32_t)expectedErrors.size(), reply->getNumErrors());
+ return false;
+ }
+ for (uint32_t i = 0; i < expectedErrors.size(); ++i) {
+ uint32_t err = reply->getError(i).getCode();
+ if (std::find(expectedErrors.begin(), expectedErrors.end(), err) == expectedErrors.end()) {
+ LOG(error, "Expected error code %d not found.", err);
+ return false;
+ }
+ }
+ } else if (reply->hasErrors()) {
+ LOG(error, "Got %d unexpected error(s):", reply->getNumErrors());
+ for(uint32_t i = 0; i < reply->getNumErrors(); ++i) {
+ LOG(error, "%d. %s", i + 1, reply->getError(i).toString().c_str());
+ }
+ return false;
+ }
+ if (!allowedValues.empty()) {
+ if (mbus::SimpleProtocol::REPLY != reply->getType()) {
+ LOG(error, "Expected reply type %d, got %d.", mbus::SimpleProtocol::REPLY, reply->getType());
+ return false;
+ }
+ string val = static_cast<mbus::SimpleReply&>(*reply).getValue();
+ if (std::find(allowedValues.begin(), allowedValues.end(), val) == allowedValues.end()) {
+ LOG(error, "Value '%s' not allowed.", val.c_str());
+ return false;
+ }
+ } else {
+ if (0 != reply->getType()) {
+ LOG(error, "Expected reply type %d, got %d.", 0, reply->getType());
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+TestFrame::testMergeOneReply(const string &recipient)
+{
+ if (!testSelect(StringList().add(recipient))) {
+ return false;
+ }
+
+ ReplyMap replies;
+ replies[recipient] = mbus::ErrorCode::NONE;
+ if (!testMergeOk(replies, StringList().add(recipient))) {
+ LOG(error, "Failed to merge reply with no error.");
+ return false;
+ }
+
+ replies[recipient] = mbus::ErrorCode::TRANSIENT_ERROR;
+ if (!testMergeError(replies, UIntList().add(mbus::ErrorCode::TRANSIENT_ERROR))) {
+ LOG(error, "Failed to merge reply with transient error.");
+ return false;
+ }
+
+ return true;
+}
+
+bool
+TestFrame::testMergeTwoReplies(const string &recipientOne, const string &recipientTwo)
+{
+ if (!testSelect(StringList().add(recipientOne).add(recipientTwo))) {
+ return false;
+ }
+
+ ReplyMap replies;
+ replies[recipientOne] = mbus::ErrorCode::NONE;
+ replies[recipientTwo] = mbus::ErrorCode::NONE;
+ if (!testMergeOk(replies, StringList().add(recipientOne).add(recipientTwo))) {
+ LOG(error, "Failed to merge two replies with no error.");
+ return false;
+ }
+
+ replies[recipientOne] = mbus::ErrorCode::TRANSIENT_ERROR;
+ replies[recipientTwo] = mbus::ErrorCode::NONE;
+ if (!testMergeError(replies, UIntList().add(mbus::ErrorCode::TRANSIENT_ERROR))) {
+ LOG(error, "Failed to merge two replies where one has transient error.");
+ return false;
+ }
+
+ replies[recipientOne] = mbus::ErrorCode::TRANSIENT_ERROR;
+ replies[recipientTwo] = mbus::ErrorCode::TRANSIENT_ERROR;
+ if (!testMergeError(replies, UIntList()
+ .add(mbus::ErrorCode::TRANSIENT_ERROR)
+ .add(mbus::ErrorCode::TRANSIENT_ERROR))) {
+ LOG(error, "Failed to merge two replies where both have transient errors.");
+ return false;
+ }
+
+ replies[recipientOne] = mbus::ErrorCode::NONE;
+ replies[recipientTwo] = DocumentProtocol::ERROR_MESSAGE_IGNORED;
+ if (!testMergeOk(replies, StringList().add(recipientOne))) {
+ LOG(error, "Failed to merge two replies where second should be ignored.");
+ return false;
+ }
+
+ replies[recipientOne] = DocumentProtocol::ERROR_MESSAGE_IGNORED;
+ replies[recipientTwo] = mbus::ErrorCode::NONE;
+ if (!testMergeOk(replies, StringList().add(recipientTwo))) {
+ LOG(error, "Failed to merge two replies where first should be ignored.");
+ return false;
+ }
+
+ replies[recipientOne] = DocumentProtocol::ERROR_MESSAGE_IGNORED;
+ replies[recipientTwo] = DocumentProtocol::ERROR_MESSAGE_IGNORED;
+ if (!testMergeError(replies, UIntList()
+ .add(DocumentProtocol::ERROR_MESSAGE_IGNORED)
+ .add(DocumentProtocol::ERROR_MESSAGE_IGNORED))) {
+ LOG(error, "Failed to merge two replies where both can be ignored.");
+ return false;
+ }
+
+ return true;
+}
+
+bool
+TestFrame::waitSlobrok(const string &pattern, uint32_t cnt)
+{
+ for (uint32_t i = 0; i < 1000; ++i) {
+ slobrok::api::MirrorAPI::SpecList res = _net->getMirror().lookup(pattern);
+ if (res.size() == cnt) {
+ return true;
+ }
+ FastOS_Thread::Sleep(10);
+ }
+ LOG(error, "Slobrok failed to resolve '%s' to %d recipients in time.", pattern.c_str(), cnt);
+ return false;
+}
+
+SystemStateHandle
+TestFrame::getSystemState()
+{
+ mbus::IProtocol::SP protocol = _mbus->getProtocol(DocumentProtocol::NAME);
+ return SystemStateHandle(static_cast<DocumentProtocol&>(*protocol).getSystemState());
+}
+
+void
+TestFrame::handleReply(mbus::Reply::UP reply)
+{
+ _msg = reply->getMessage();
+ _handler.handleReply(std::move(reply));
+}
diff --git a/documentapi/src/tests/policies/testframe.h b/documentapi/src/tests/policies/testframe.h
new file mode 100644
index 00000000000..6c3080974d1
--- /dev/null
+++ b/documentapi/src/tests/policies/testframe.h
@@ -0,0 +1,219 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <vespa/documentapi/messagebus/documentprotocol.h>
+#include <vespa/documentapi/messagebus/systemstate/systemstatehandle.h>
+#include <vespa/messagebus/messagebus.h>
+#include <vespa/messagebus/network/identity.h>
+#include <vespa/messagebus/network/inetwork.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/documentapi/loadtypes/loadtypeset.h>
+
+using documentapi::string;
+
+class TestFrame : public mbus::IReplyHandler {
+private:
+ string _identity;
+ std::shared_ptr<mbus::Slobrok> _slobrok;
+ documentapi::LoadTypeSet _set;
+ std::shared_ptr<mbus::INetwork> _net;
+ std::shared_ptr<mbus::MessageBus> _mbus;
+ mbus::Message::UP _msg;
+ mbus::HopSpec _hop;
+ mbus::Receptor _handler;
+
+ TestFrame &operator=(const TestFrame &); // hide
+
+public:
+ /**
+ * Convenience typedefs.
+ */
+ typedef std::map<string, uint32_t> ReplyMap;
+
+ /**
+ * Create a named test frame.
+ *
+ * @param identity The identity to use for the server.
+ */
+ TestFrame(const document::DocumentTypeRepo::SP &repo,
+ const string &ident = "anonymous");
+
+ /**
+ * Create a test frame running on the same slobrok and mbus as another.
+ *
+ * @param frame The frame whose internals to share.
+ */
+ TestFrame(TestFrame &frame);
+
+ /**
+ * Cleans up allocated resources.
+ */
+ virtual ~TestFrame();
+
+ /**
+ * Routes the contained message based on the current setup, and returns the leaf send contexts.
+ *
+ * @param selected The list to add the selected recipients to.
+ * @param numExpected The expected number of contexts.
+ * @return True if everything was ok.
+ */
+ bool select(std::vector<mbus::RoutingNode*> &selected, uint32_t numExpected);
+
+ /**
+ * Ensures that the current setup selects a given set of routes.
+ *
+ * @param expected A list of expected route leaf nodes.
+ * @return True if everything was ok.
+ */
+ bool testSelect(const std::vector<string> &expected);
+
+ /**
+ * This is a convenience method for invoking {@link #assertMerge(std::map,std::vector,std::vector)} with
+ * no expected value.
+ *
+ * @param replies The errors to set in the leaf node replies.
+ * @param expectedErrors The list of expected errors in the merged reply.
+ * @return True if everything was ok.
+ */
+ bool testMergeError(const ReplyMap &replies, const std::vector<uint32_t> &expectedErrors);
+
+ /**
+ * This is a convenience method for invoking {@link #assertMerge(std::map,std::vector,std::vector)} with
+ * no expected errors.
+ *
+ * @param replies The errors to set in the leaf node replies.
+ * @param allowedValues The list of allowed values in the final reply.
+ * @return True if everything was ok.
+ */
+ bool testMergeOk(const ReplyMap &replies, const std::vector<string> &allowedValues);
+
+ /**
+ * Ensures that the current setup generates as many leaf nodes as there are members of the errors argument. Each
+ * error is then given one of these errors, and the method then ensures that the single returned reply contains the
+ * given list of expected errors. Finally, if the expected value argument is non-null, this method ensures that the
+ * reply is a SimpleReply whose string value exists in the allowed list.
+ *
+ * @param replies The errors to set in the leaf node replies.
+ * @param expectedErrors The list of expected errors in the merged reply.
+ * @param allowedValues The list of allowed values in the final reply.
+ * @return True if everything was ok.
+ */
+ bool testMerge(const ReplyMap &replies,
+ const std::vector<uint32_t> &expectedErrors,
+ const std::vector<string> &allowedValues);
+
+ /**
+ * Ensures that the current setup chooses a single recipient, and that it merges similarly to how the
+ * {@link DocumentProtocol} would merge these.
+ *
+ * @param recipient The expected recipient.
+ * @return True if everything was ok.
+ */
+ bool testMergeOneReply(const string &recipient);
+
+ /**
+ * Ensures that the current setup will choose the two given recipients, and that it merges similarly to how the
+ * {@link DocumentProtocol} would merge these.
+ *
+ * @param recipientOne The first expected recipient.
+ * @param recipientTwo The second expected recipient.
+ */
+ bool testMergeTwoReplies(const string &recipientOne, const string &recipientTwo);
+
+ /**
+ * Waits for a given service pattern to resolve to the given number of hits in the local slobrok.
+ *
+ * @param pattern The pattern to lookup.
+ * @param cnt The number of entries to wait for.
+ * @return True if the expected number of entries was found.
+ */
+ bool waitSlobrok(const string &pattern, uint32_t cnt);
+
+ /**
+ * Returns the identity of this frame.
+ *
+ * @return The ident string.
+ */
+ const string &getIdentity() { return _identity; }
+
+ /**
+ * Returns the private slobrok server.
+ *
+ * @return The slobrok.
+ */
+ mbus::Slobrok &getSlobrok() { return *_slobrok; }
+
+ /**
+ * Returns the private message bus.
+ *
+ * @return The bus.
+ */
+ mbus::MessageBus &getMessageBus() { return *_mbus; }
+
+ /**
+ * Returns the private network layer.
+ *
+ * @return The network.
+ */
+ mbus::INetwork &getNetwork() { return *_net; }
+
+ /**
+ * Returns the message being tested.
+ *
+ * @return The message.
+ */
+ mbus::Message::UP getMessage() { return std::move(_msg); }
+
+ /**
+ * Sets the message being tested.
+ *
+ * @param msg The message to set.
+ */
+ mbus::Message::UP setMessage(mbus::Message::UP msg) {
+ std::swap(msg, _msg);
+ return std::move(msg);
+ }
+
+ /**
+ * Sets the spec of the hop to test with.
+ *
+ * @param hop The spec to set.
+ */
+ void setHop(const mbus::HopSpec &hop);
+
+ /**
+ * Returns the reply receptor used by this frame. All messages tested are tagged with this receptor, so after a
+ * successful select, the receptor should contain a non-null reply.
+ *
+ * @return The reply receptor.
+ */
+ mbus::Receptor &getReceptor() { return _handler; }
+
+ /**
+ * Returns the system state from contained document protocol.
+ *
+ * @return Handle to the system state.
+ */
+ documentapi::SystemStateHandle getSystemState();
+
+ // Implements IReplyHandler.
+ void handleReply(mbus::Reply::UP reply);
+};
+
+class UIntList : public std::vector<uint32_t> {
+public:
+ UIntList &add(uint32_t err) {
+ std::vector<uint32_t>::push_back(err);
+ return *this;
+ }
+};
+
+class StringList : public std::vector<string> {
+public:
+ StringList &add(const string &val) {
+ std::vector<string>::push_back(val);
+ return *this;
+ }
+};
+
diff --git a/documentapi/src/tests/policyfactory/.gitignore b/documentapi/src/tests/policyfactory/.gitignore
new file mode 100644
index 00000000000..8ab5fc6c580
--- /dev/null
+++ b/documentapi/src/tests/policyfactory/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+policyfactory_test
+documentapi_policyfactory_test_app
diff --git a/documentapi/src/tests/policyfactory/CMakeLists.txt b/documentapi/src/tests/policyfactory/CMakeLists.txt
new file mode 100644
index 00000000000..c533847ff36
--- /dev/null
+++ b/documentapi/src/tests/policyfactory/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(documentapi_policyfactory_test_app
+ SOURCES
+ policyfactory.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_policyfactory_test_app COMMAND documentapi_policyfactory_test_app)
diff --git a/documentapi/src/tests/policyfactory/DESC b/documentapi/src/tests/policyfactory/DESC
new file mode 100644
index 00000000000..faf40102408
--- /dev/null
+++ b/documentapi/src/tests/policyfactory/DESC
@@ -0,0 +1 @@
+policyfactory test. Take a look at policyfactory.cpp for details.
diff --git a/documentapi/src/tests/policyfactory/FILES b/documentapi/src/tests/policyfactory/FILES
new file mode 100644
index 00000000000..744e02bc7d6
--- /dev/null
+++ b/documentapi/src/tests/policyfactory/FILES
@@ -0,0 +1 @@
+policyfactory.cpp
diff --git a/documentapi/src/tests/policyfactory/policyfactory.cpp b/documentapi/src/tests/policyfactory/policyfactory.cpp
new file mode 100644
index 00000000000..3caba70496b
--- /dev/null
+++ b/documentapi/src/tests/policyfactory/policyfactory.cpp
@@ -0,0 +1,115 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP("policyfactory_test");
+
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/documentapi/messagebus/documentprotocol.h>
+#include <vespa/documentapi/messagebus/messages/removedocumentmessage.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using document::DocumentTypeRepo;
+using namespace documentapi;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Utilities
+//
+///////////////////////////////////////////////////////////////////////////////
+
+class MyPolicy : public mbus::IRoutingPolicy {
+private:
+ string _param;
+public:
+ MyPolicy(const string &param);
+ void select(mbus::RoutingContext &ctx);
+ void merge(mbus::RoutingContext &ctx);
+};
+
+MyPolicy::MyPolicy(const string &param) :
+ _param(param)
+{
+ // empty
+}
+
+void
+MyPolicy::select(mbus::RoutingContext &ctx)
+{
+ ctx.setError(DocumentProtocol::ERROR_POLICY_FAILURE, _param);
+}
+
+void
+MyPolicy::merge(mbus::RoutingContext &ctx)
+{
+ (void)ctx;
+ LOG_ASSERT(false);
+}
+
+class MyFactory : public IRoutingPolicyFactory {
+public:
+ mbus::IRoutingPolicy::UP createPolicy(const string &param) const;
+};
+
+mbus::IRoutingPolicy::UP
+MyFactory::createPolicy(const string &param) const
+{
+ return mbus::IRoutingPolicy::UP(new MyPolicy(param));
+}
+
+mbus::Message::UP
+createMessage()
+{
+ mbus::Message::UP ret(new RemoveDocumentMessage(document::DocumentId("doc:scheme:")));
+ ret->getTrace().setLevel(9);
+ return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Tests
+//
+///////////////////////////////////////////////////////////////////////////////
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("policyfactory_test");
+
+ DocumentTypeRepo::SP repo(new DocumentTypeRepo);
+ mbus::Slobrok slobrok;
+ LoadTypeSet loadTypes;
+ mbus::TestServer
+ srv(mbus::MessageBusParams()
+ .addProtocol(mbus::IProtocol::SP(new DocumentProtocol(
+ loadTypes, repo))),
+ mbus::RPCNetworkParams().setSlobrokConfig(slobrok.config()));
+ mbus::Receptor handler;
+ mbus::SourceSession::UP src = srv.mb.createSourceSession(mbus::SourceSessionParams().setReplyHandler(handler));
+
+ mbus::Route route = mbus::Route::parse("[MyPolicy]");
+ ASSERT_TRUE(src->send(createMessage(), route).isAccepted());
+ mbus::Reply::UP reply = static_cast<mbus::Receptor&>(src->getReplyHandler()).getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ fprintf(stderr, "%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)mbus::ErrorCode::UNKNOWN_POLICY, reply->getError(0).getCode());
+
+ mbus::IProtocol::SP obj = srv.mb.getProtocol(DocumentProtocol::NAME);
+ DocumentProtocol *protocol = dynamic_cast<DocumentProtocol*>(obj.get());
+ ASSERT_TRUE(protocol != NULL);
+ protocol->putRoutingPolicyFactory("MyPolicy", IRoutingPolicyFactory::SP(new MyFactory()));
+
+ ASSERT_TRUE(src->send(createMessage(), route).isAccepted());
+ reply = static_cast<mbus::Receptor&>(src->getReplyHandler()).getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ fprintf(stderr, "%s", reply->getTrace().toString().c_str());
+ EXPECT_EQUAL(1u, reply->getNumErrors());
+ EXPECT_EQUAL((uint32_t)DocumentProtocol::ERROR_POLICY_FAILURE, reply->getError(0).getCode());
+
+ TEST_DONE();
+}
diff --git a/documentapi/src/tests/priority/.gitignore b/documentapi/src/tests/priority/.gitignore
new file mode 100644
index 00000000000..b0e5123f142
--- /dev/null
+++ b/documentapi/src/tests/priority/.gitignore
@@ -0,0 +1 @@
+documentapi_priority_test_app
diff --git a/documentapi/src/tests/priority/CMakeLists.txt b/documentapi/src/tests/priority/CMakeLists.txt
new file mode 100644
index 00000000000..289ea4b4ebe
--- /dev/null
+++ b/documentapi/src/tests/priority/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(documentapi_priority_test_app
+ SOURCES
+ priority.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_priority_test_app COMMAND documentapi_priority_test_app)
diff --git a/documentapi/src/tests/priority/DESC b/documentapi/src/tests/priority/DESC
new file mode 100644
index 00000000000..d213e9de057
--- /dev/null
+++ b/documentapi/src/tests/priority/DESC
@@ -0,0 +1 @@
+priority test. Take a look at priority.cpp for details.
diff --git a/documentapi/src/tests/priority/FILES b/documentapi/src/tests/priority/FILES
new file mode 100644
index 00000000000..4a9bd82566a
--- /dev/null
+++ b/documentapi/src/tests/priority/FILES
@@ -0,0 +1 @@
+priority.cpp
diff --git a/documentapi/src/tests/priority/priority.cpp b/documentapi/src/tests/priority/priority.cpp
new file mode 100644
index 00000000000..3ad0e2041cf
--- /dev/null
+++ b/documentapi/src/tests/priority/priority.cpp
@@ -0,0 +1,59 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP("priority_test");
+
+#include <fstream>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/documentapi/messagebus/priority.h>
+
+using namespace documentapi;
+
+TEST_SETUP(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("priority_test");
+
+ std::vector<int32_t> expected;
+ expected.push_back(Priority::PRI_HIGHEST);
+ expected.push_back(Priority::PRI_VERY_HIGH);
+ expected.push_back(Priority::PRI_HIGH_1);
+ expected.push_back(Priority::PRI_HIGH_2);
+ expected.push_back(Priority::PRI_HIGH_3);
+ expected.push_back(Priority::PRI_NORMAL_1);
+ expected.push_back(Priority::PRI_NORMAL_2);
+ expected.push_back(Priority::PRI_NORMAL_3);
+ expected.push_back(Priority::PRI_NORMAL_4);
+ expected.push_back(Priority::PRI_NORMAL_5);
+ expected.push_back(Priority::PRI_NORMAL_6);
+ expected.push_back(Priority::PRI_LOW_1);
+ expected.push_back(Priority::PRI_LOW_2);
+ expected.push_back(Priority::PRI_LOW_3);
+ expected.push_back(Priority::PRI_VERY_LOW);
+ expected.push_back(Priority::PRI_LOWEST);
+
+ std::ifstream in;
+ in.open("../../../test/crosslanguagefiles/5.1-Priority.txt");
+ ASSERT_TRUE(in.good());
+ while (in) {
+ std::string str;
+ in >> str;
+ if (str.empty()) {
+ continue;
+ }
+ size_t pos = str.find(":");
+ ASSERT_TRUE(pos != std::string::npos);
+ int32_t pri = atoi(str.substr(pos + 1).c_str());
+ ASSERT_EQUAL(Priority::getPriority(str.substr(0, pos)), pri);
+
+ std::vector<int32_t>::iterator it =
+ std::find(expected.begin(), expected.end(), pri);
+ ASSERT_TRUE(it != expected.end());
+ expected.erase(it);
+ }
+ ASSERT_TRUE(expected.empty());
+
+ TEST_DONE();
+}
diff --git a/documentapi/src/tests/replymerger/.gitignore b/documentapi/src/tests/replymerger/.gitignore
new file mode 100644
index 00000000000..932bb09e490
--- /dev/null
+++ b/documentapi/src/tests/replymerger/.gitignore
@@ -0,0 +1 @@
+documentapi_replymerger_test_app
diff --git a/documentapi/src/tests/replymerger/CMakeLists.txt b/documentapi/src/tests/replymerger/CMakeLists.txt
new file mode 100644
index 00000000000..9faa203fb2b
--- /dev/null
+++ b/documentapi/src/tests/replymerger/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(documentapi_replymerger_test_app
+ SOURCES
+ replymerger_test.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_replymerger_test_app COMMAND documentapi_replymerger_test_app)
diff --git a/documentapi/src/tests/replymerger/DESC b/documentapi/src/tests/replymerger/DESC
new file mode 100644
index 00000000000..ca179fc0da0
--- /dev/null
+++ b/documentapi/src/tests/replymerger/DESC
@@ -0,0 +1 @@
+replymerger test. Take a look at replymerger.cpp for details.
diff --git a/documentapi/src/tests/replymerger/FILES b/documentapi/src/tests/replymerger/FILES
new file mode 100644
index 00000000000..5056276d197
--- /dev/null
+++ b/documentapi/src/tests/replymerger/FILES
@@ -0,0 +1 @@
+replymerger.cpp
diff --git a/documentapi/src/tests/replymerger/replymerger_test.cpp b/documentapi/src/tests/replymerger/replymerger_test.cpp
new file mode 100644
index 00000000000..2e1551e8dca
--- /dev/null
+++ b/documentapi/src/tests/replymerger/replymerger_test.cpp
@@ -0,0 +1,304 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/log/log.h>
+LOG_SETUP("replymerger_test");
+
+#include <vespa/fastos/fastos.h>
+#include <iostream>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/documentapi/messagebus/replymerger.h>
+#include <vespa/documentapi/messagebus/documentprotocol.h>
+#include <vespa/documentapi/messagebus/messages/removedocumentreply.h>
+#include <vespa/documentapi/messagebus/messages/updatedocumentreply.h>
+#include <vespa/documentapi/messagebus/messages/getdocumentreply.h>
+#include <vespa/messagebus/emptyreply.h>
+
+using namespace documentapi;
+
+class Test : public vespalib::TestApp
+{
+ static void assertReplyErrorsMatch(const mbus::Reply& r,
+ const std::vector<mbus::Error>& errors);
+public:
+ int Main();
+
+ void mergingGenericRepliesWithNoErrorsPicksFirstReply();
+ void mergingSingleReplyWithOneErrorReturnsEmptyReplyWithError();
+ void mergingSingleReplyWithMultipleErrorsReturnsEmptyReplyWithAllErrors();
+ void mergingMultipleRepliesWithMultipleErrorsReturnsEmptyReplyWithAllErrors();
+ void returnIgnoredReplyWhenAllRepliesHaveOnlyIgnoredErrors();
+ void successfulReplyTakesPrecedenceOverIgnoredReplyWhenNoErrors();
+ void nonIgnoredErrorTakesPrecedence();
+ void returnRemoveDocumentReplyWhereDocWasFound();
+ void returnFirstRemoveDocumentReplyIfNoDocsWereFound();
+ void returnUpdateDocumentReplyWhereDocWasFound();
+ void returnGetDocumentReplyWhereDocWasFound();
+ void mergingZeroRepliesReturnsDefaultEmptyReply();
+};
+
+TEST_APPHOOK(Test);
+
+void
+Test::mergingGenericRepliesWithNoErrorsPicksFirstReply()
+{
+ mbus::EmptyReply r1;
+ mbus::EmptyReply r2;
+ mbus::EmptyReply r3;
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ merger.merge(1, r2);
+ merger.merge(2, r3);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_TRUE(ret.isSuccessful());
+ ASSERT_FALSE(ret.hasGeneratedReply());
+ EXPECT_EQUAL(0u, ret.getSuccessfulReplyIndex());
+}
+
+void
+Test::mergingSingleReplyWithOneErrorReturnsEmptyReplyWithError()
+{
+ mbus::EmptyReply r1;
+ std::vector<mbus::Error> errors = { mbus::Error(1234, "oh no!") };
+ r1.addError(errors[0]);
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_FALSE(ret.isSuccessful());
+ ASSERT_TRUE(ret.hasGeneratedReply());
+ std::unique_ptr<mbus::Reply> gen(ret.releaseGeneratedReply());
+ assertReplyErrorsMatch(*gen, errors);
+}
+
+void
+Test::mergingSingleReplyWithMultipleErrorsReturnsEmptyReplyWithAllErrors()
+{
+ mbus::EmptyReply r1;
+ std::vector<mbus::Error> errors = {
+ mbus::Error(1234, "oh no!"),
+ mbus::Error(4567, "oh dear!")
+ };
+ r1.addError(errors[0]);
+ r1.addError(errors[1]);
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_FALSE(ret.isSuccessful());
+ ASSERT_TRUE(ret.hasGeneratedReply());
+ std::unique_ptr<mbus::Reply> gen(ret.releaseGeneratedReply());
+ assertReplyErrorsMatch(*gen, errors);
+}
+
+void
+Test::mergingMultipleRepliesWithMultipleErrorsReturnsEmptyReplyWithAllErrors()
+{
+ mbus::EmptyReply r1;
+ mbus::EmptyReply r2;
+ std::vector<mbus::Error> errors = {
+ mbus::Error(1234, "oh no!"),
+ mbus::Error(4567, "oh dear!"),
+ mbus::Error(678, "omg!")
+ };
+ r1.addError(errors[0]);
+ r1.addError(errors[1]);
+ r2.addError(errors[2]);
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ merger.merge(1, r2);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_FALSE(ret.isSuccessful());
+ ASSERT_TRUE(ret.hasGeneratedReply());
+ std::unique_ptr<mbus::Reply> gen(ret.releaseGeneratedReply());
+ assertReplyErrorsMatch(*gen, errors);
+}
+
+void
+Test::returnIgnoredReplyWhenAllRepliesHaveOnlyIgnoredErrors()
+{
+ mbus::EmptyReply r1;
+ mbus::EmptyReply r2;
+ std::vector<mbus::Error> errors = {
+ mbus::Error(DocumentProtocol::ERROR_MESSAGE_IGNORED, "oh no!"),
+ mbus::Error(DocumentProtocol::ERROR_MESSAGE_IGNORED, "oh dear!"),
+ mbus::Error(DocumentProtocol::ERROR_MESSAGE_IGNORED, "omg!")
+ };
+ r1.addError(errors[0]);
+ r1.addError(errors[1]);
+ r2.addError(errors[2]);
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ merger.merge(1, r2);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_FALSE(ret.isSuccessful());
+ ASSERT_TRUE(ret.hasGeneratedReply());
+ std::unique_ptr<mbus::Reply> gen(ret.releaseGeneratedReply());
+ // Only first ignore error from each reply.
+ assertReplyErrorsMatch(*gen, { errors[0], errors[2] });
+}
+
+void
+Test::successfulReplyTakesPrecedenceOverIgnoredReplyWhenNoErrors()
+{
+ mbus::EmptyReply r1;
+ mbus::EmptyReply r2;
+ std::vector<mbus::Error> errors = {
+ mbus::Error(DocumentProtocol::ERROR_MESSAGE_IGNORED, "oh no!"),
+ };
+ r1.addError(errors[0]);
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ merger.merge(1, r2);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_TRUE(ret.isSuccessful());
+ ASSERT_FALSE(ret.hasGeneratedReply());
+ EXPECT_EQUAL(1u, ret.getSuccessfulReplyIndex());
+}
+
+void
+Test::nonIgnoredErrorTakesPrecedence()
+{
+ mbus::EmptyReply r1;
+ mbus::EmptyReply r2;
+ std::vector<mbus::Error> errors = {
+ mbus::Error(DocumentProtocol::ERROR_MESSAGE_IGNORED, "oh no!"),
+ mbus::Error(DocumentProtocol::ERROR_ABORTED, "kablammo!"),
+ mbus::Error(DocumentProtocol::ERROR_MESSAGE_IGNORED, "omg!")
+ };
+ r1.addError(errors[0]);
+ r1.addError(errors[1]);
+ r2.addError(errors[2]);
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ merger.merge(1, r2);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_FALSE(ret.isSuccessful());
+ ASSERT_TRUE(ret.hasGeneratedReply());
+ std::unique_ptr<mbus::Reply> gen(ret.releaseGeneratedReply());
+ // All errors from replies with errors are included, not those that
+ // are fully ignored.
+ assertReplyErrorsMatch(*gen, { errors[0], errors[1] });
+}
+
+void
+Test::returnRemoveDocumentReplyWhereDocWasFound()
+{
+ RemoveDocumentReply r1;
+ RemoveDocumentReply r2;
+ RemoveDocumentReply r3;
+ r1.setWasFound(false);
+ r2.setWasFound(true);
+ r3.setWasFound(false);
+
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ merger.merge(1, r2);
+ merger.merge(2, r3);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_TRUE(ret.isSuccessful());
+ ASSERT_FALSE(ret.hasGeneratedReply());
+ ASSERT_EQUAL(1u, ret.getSuccessfulReplyIndex());
+}
+
+void
+Test::returnFirstRemoveDocumentReplyIfNoDocsWereFound()
+{
+ RemoveDocumentReply r1;
+ RemoveDocumentReply r2;
+ r1.setWasFound(false);
+ r2.setWasFound(false);
+
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ merger.merge(1, r2);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_TRUE(ret.isSuccessful());
+ ASSERT_FALSE(ret.hasGeneratedReply());
+ ASSERT_EQUAL(0u, ret.getSuccessfulReplyIndex());
+}
+
+void
+Test::returnUpdateDocumentReplyWhereDocWasFound()
+{
+ UpdateDocumentReply r1;
+ UpdateDocumentReply r2;
+ UpdateDocumentReply r3;
+ r1.setWasFound(false);
+ r2.setWasFound(true); // return first reply
+ r3.setWasFound(true);
+
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ merger.merge(1, r2);
+ merger.merge(2, r3);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_TRUE(ret.isSuccessful());
+ ASSERT_FALSE(ret.hasGeneratedReply());
+ ASSERT_EQUAL(1u, ret.getSuccessfulReplyIndex());
+}
+
+void
+Test::returnGetDocumentReplyWhereDocWasFound()
+{
+ GetDocumentReply r1;
+ GetDocumentReply r2;
+ GetDocumentReply r3;
+ r2.setLastModified(12345ULL);
+
+ ReplyMerger merger;
+ merger.merge(0, r1);
+ merger.merge(1, r2);
+ merger.merge(2, r3);
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_TRUE(ret.isSuccessful());
+ ASSERT_FALSE(ret.hasGeneratedReply());
+ ASSERT_EQUAL(1u, ret.getSuccessfulReplyIndex());
+}
+
+void
+Test::assertReplyErrorsMatch(const mbus::Reply& r,
+ const std::vector<mbus::Error>& errors)
+{
+ ASSERT_EQUAL(r.getNumErrors(), errors.size());
+ for (size_t i = 0; i < errors.size(); ++i) {
+ ASSERT_EQUAL(errors[i].getCode(), r.getError(i).getCode());
+ ASSERT_EQUAL(errors[i].getMessage(), r.getError(i).getMessage());
+ }
+}
+
+void
+Test::mergingZeroRepliesReturnsDefaultEmptyReply()
+{
+ ReplyMerger merger;
+ ReplyMerger::Result ret(merger.mergedReply());
+ ASSERT_FALSE(ret.isSuccessful());
+ ASSERT_TRUE(ret.hasGeneratedReply());
+ std::unique_ptr<mbus::Reply> gen(ret.releaseGeneratedReply());
+ ASSERT_TRUE(dynamic_cast<mbus::EmptyReply*>(gen.get()) != 0);
+ assertReplyErrorsMatch(*gen, {});
+}
+
+#ifdef RUN_TEST
+# error Someone defined RUN_TEST already! Oh no!
+#endif
+#define RUN_TEST(f) \
+ std::cerr << "running test case '" #f "'\n"; \
+ f(); TEST_FLUSH();
+
+int
+Test::Main()
+{
+ TEST_INIT("replymerger_test");
+
+ RUN_TEST(mergingGenericRepliesWithNoErrorsPicksFirstReply);
+ RUN_TEST(mergingSingleReplyWithOneErrorReturnsEmptyReplyWithError);
+ RUN_TEST(mergingSingleReplyWithMultipleErrorsReturnsEmptyReplyWithAllErrors);
+ RUN_TEST(mergingMultipleRepliesWithMultipleErrorsReturnsEmptyReplyWithAllErrors);
+ RUN_TEST(returnIgnoredReplyWhenAllRepliesHaveOnlyIgnoredErrors);
+ RUN_TEST(successfulReplyTakesPrecedenceOverIgnoredReplyWhenNoErrors);
+ RUN_TEST(nonIgnoredErrorTakesPrecedence);
+ RUN_TEST(returnRemoveDocumentReplyWhereDocWasFound);
+ RUN_TEST(returnFirstRemoveDocumentReplyIfNoDocsWereFound);
+ RUN_TEST(returnUpdateDocumentReplyWhereDocWasFound);
+ RUN_TEST(returnGetDocumentReplyWhereDocWasFound);
+ RUN_TEST(mergingZeroRepliesReturnsDefaultEmptyReply);
+
+ TEST_DONE();
+}
diff --git a/documentapi/src/tests/routablefactory/.gitignore b/documentapi/src/tests/routablefactory/.gitignore
new file mode 100644
index 00000000000..bf482dc22db
--- /dev/null
+++ b/documentapi/src/tests/routablefactory/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+routablefactory_test
+documentapi_routablefactory_test_app
diff --git a/documentapi/src/tests/routablefactory/CMakeLists.txt b/documentapi/src/tests/routablefactory/CMakeLists.txt
new file mode 100644
index 00000000000..ba6ea53bf8f
--- /dev/null
+++ b/documentapi/src/tests/routablefactory/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(documentapi_routablefactory_test_app
+ SOURCES
+ routablefactory.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_routablefactory_test_app COMMAND documentapi_routablefactory_test_app)
diff --git a/documentapi/src/tests/routablefactory/DESC b/documentapi/src/tests/routablefactory/DESC
new file mode 100644
index 00000000000..11c0cb8c0d3
--- /dev/null
+++ b/documentapi/src/tests/routablefactory/DESC
@@ -0,0 +1 @@
+routablefactory test. Take a look at routablefactory.cpp for details.
diff --git a/documentapi/src/tests/routablefactory/FILES b/documentapi/src/tests/routablefactory/FILES
new file mode 100644
index 00000000000..6176a49814e
--- /dev/null
+++ b/documentapi/src/tests/routablefactory/FILES
@@ -0,0 +1 @@
+routablefactory.cpp
diff --git a/documentapi/src/tests/routablefactory/routablefactory.cpp b/documentapi/src/tests/routablefactory/routablefactory.cpp
new file mode 100644
index 00000000000..5bd7ee48471
--- /dev/null
+++ b/documentapi/src/tests/routablefactory/routablefactory.cpp
@@ -0,0 +1,242 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP("routablefactory_test");
+
+#include <vespa/document/repo/documenttyperepo.h>
+#include <vespa/documentapi/messagebus/documentprotocol.h>
+#include <vespa/documentapi/messagebus/routablefactories51.h>
+#include <vespa/messagebus/testlib/receptor.h>
+#include <vespa/messagebus/testlib/slobrok.h>
+#include <vespa/messagebus/testlib/testserver.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using document::DocumentTypeRepo;
+using namespace documentapi;
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Utilities
+//
+///////////////////////////////////////////////////////////////////////////////
+
+class MyReply : public DocumentReply {
+public:
+ enum {
+ TYPE = 777
+ };
+
+ MyReply() :
+ DocumentReply(TYPE) {
+ // empty
+ }
+};
+
+class MyMessage : public DocumentMessage {
+public:
+ enum {
+ TYPE = 666
+ };
+
+ MyMessage() {
+ getTrace().setLevel(9);
+ }
+
+ DocumentReply::UP doCreateReply() const {
+ return DocumentReply::UP(new MyReply());
+ }
+
+ uint32_t getType() const {
+ return TYPE;
+ }
+};
+
+class MyMessageFactory : public RoutableFactories51::DocumentMessageFactory {
+protected:
+ DocumentMessage::UP doDecode(document::ByteBuffer &buf) const {
+ (void)buf;
+ return DocumentMessage::UP(new MyMessage());
+ }
+
+ bool doEncode(const DocumentMessage &msg, vespalib::GrowableByteBuffer &buf) const {
+ (void)msg;
+ (void)buf;
+ return true;
+ }
+};
+
+class MyReplyFactory : public RoutableFactories51::DocumentReplyFactory {
+protected:
+ DocumentReply::UP doDecode(document::ByteBuffer &buf) const {
+ (void)buf;
+ return DocumentReply::UP(new MyReply());
+ }
+
+ bool doEncode(const DocumentReply &reply, vespalib::GrowableByteBuffer &buf) const {
+ (void)reply;
+ (void)buf;
+ return true;
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Setup
+//
+///////////////////////////////////////////////////////////////////////////////
+
+class TestData {
+ const DocumentTypeRepo::SP _repo;
+
+public:
+ mbus::Slobrok _slobrok;
+ LoadTypeSet _loadTypes;
+ DocumentProtocol::SP _srcProtocol;
+ mbus::TestServer _srcServer;
+ mbus::SourceSession::UP _srcSession;
+ mbus::Receptor _srcHandler;
+ DocumentProtocol::SP _dstProtocol;
+ mbus::TestServer _dstServer;
+ mbus::DestinationSession::UP _dstSession;
+ mbus::Receptor _dstHandler;
+
+public:
+ TestData();
+ bool start();
+};
+
+class Test : public vespalib::TestApp {
+protected:
+ void testFactory(TestData &data);
+
+public:
+ int Main();
+};
+
+TEST_APPHOOK(Test);
+
+TestData::TestData() :
+ _repo(new DocumentTypeRepo),
+ _slobrok(),
+ _loadTypes(),
+ _srcProtocol(new DocumentProtocol(_loadTypes, _repo)),
+ _srcServer(mbus::MessageBusParams().addProtocol(_srcProtocol),
+ mbus::RPCNetworkParams().setSlobrokConfig(_slobrok.config())),
+ _srcSession(),
+ _srcHandler(),
+ _dstProtocol(new DocumentProtocol(_loadTypes, _repo)),
+ _dstServer(mbus::MessageBusParams().addProtocol(_dstProtocol),
+ mbus::RPCNetworkParams().setIdentity(mbus::Identity("dst")).setSlobrokConfig(_slobrok.config())),
+ _dstSession(),
+ _dstHandler()
+{
+ // empty
+}
+
+bool
+TestData::start()
+{
+ _srcSession = _srcServer.mb.createSourceSession(mbus::SourceSessionParams().setReplyHandler(_srcHandler));
+ if (_srcSession.get() == NULL) {
+ return false;
+ }
+ _dstSession = _dstServer.mb.createDestinationSession(mbus::DestinationSessionParams().setName("session").setMessageHandler(_dstHandler));
+ if (_dstSession.get() == NULL) {
+ return false;
+ }
+ if (!_srcServer.waitSlobrok("dst/session", 1u)) {
+ return false;
+ }
+ return true;
+}
+
+int
+Test::Main()
+{
+ TEST_INIT("routablefactory_test");
+
+ TestData data;
+ ASSERT_TRUE(data.start());
+
+ testFactory(data); TEST_FLUSH();
+
+ TEST_DONE();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Tests
+//
+///////////////////////////////////////////////////////////////////////////////
+
+void
+Test::testFactory(TestData &data)
+{
+ mbus::Route route = mbus::Route::parse("dst/session");
+
+ // Source should fail to encode the message.
+ EXPECT_TRUE(data._srcSession->send(mbus::Message::UP(new MyMessage()), route).isAccepted());
+ mbus::Reply::UP reply = data._srcHandler.getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ fprintf(stderr, "%s\n", reply->getTrace().toString().c_str());
+ ASSERT_TRUE(reply->hasErrors());
+ EXPECT_EQUAL((uint32_t)mbus::ErrorCode::ENCODE_ERROR, reply->getError(0).getCode());
+ EXPECT_EQUAL("", reply->getError(0).getService());
+
+ // Destination should fail to decode the message.
+ data._srcProtocol->putRoutableFactory(MyMessage::TYPE, IRoutableFactory::SP(new MyMessageFactory()),
+ vespalib::VersionSpecification());
+ EXPECT_TRUE(data._srcSession->send(mbus::Message::UP(new MyMessage()), route).isAccepted());
+ reply = data._srcHandler.getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ fprintf(stderr, "%s\n", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(reply->hasErrors());
+ EXPECT_EQUAL((uint32_t)mbus::ErrorCode::DECODE_ERROR, reply->getError(0).getCode());
+ EXPECT_EQUAL("dst/session", reply->getError(0).getService());
+
+ // Destination should fail to encode the reply->
+ data._dstProtocol->putRoutableFactory(MyMessage::TYPE, IRoutableFactory::SP(new MyMessageFactory()),
+ vespalib::VersionSpecification());
+ EXPECT_TRUE(data._srcSession->send(mbus::Message::UP(new MyMessage()), route).isAccepted());
+ mbus::Message::UP msg = data._dstHandler.getMessage(600);
+ ASSERT_TRUE(msg.get() != NULL);
+ reply.reset(new MyReply());
+ reply->swapState(*msg);
+ data._dstSession->reply(std::move(reply));
+ reply = data._srcHandler.getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ fprintf(stderr, "%s\n", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(reply->hasErrors());
+ EXPECT_EQUAL((uint32_t)mbus::ErrorCode::ENCODE_ERROR, reply->getError(0).getCode());
+ EXPECT_EQUAL("dst/session", reply->getError(0).getService());
+
+ // Source should fail to decode the reply.
+ data._dstProtocol->putRoutableFactory(MyReply::TYPE, IRoutableFactory::SP(new MyReplyFactory()),
+ vespalib::VersionSpecification());
+ EXPECT_TRUE(data._srcSession->send(mbus::Message::UP(new MyMessage()), route).isAccepted());
+ msg = data._dstHandler.getMessage(600);
+ ASSERT_TRUE(msg.get() != NULL);
+ reply.reset(new MyReply());
+ reply->swapState(*msg);
+ data._dstSession->reply(std::move(reply));
+ reply = data._srcHandler.getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ fprintf(stderr, "%s\n", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(reply->hasErrors());
+ EXPECT_EQUAL((uint32_t)mbus::ErrorCode::DECODE_ERROR, reply->getError(0).getCode());
+ EXPECT_EQUAL("", reply->getError(0).getService());
+
+ // All should succeed.
+ data._srcProtocol->putRoutableFactory(MyReply::TYPE, IRoutableFactory::SP(new MyReplyFactory()),
+ vespalib::VersionSpecification());
+ EXPECT_TRUE(data._srcSession->send(mbus::Message::UP(new MyMessage()), route).isAccepted());
+ msg = data._dstHandler.getMessage(600);
+ ASSERT_TRUE(msg.get() != NULL);
+ reply.reset(new MyReply());
+ reply->swapState(*msg);
+ data._dstSession->reply(std::move(reply));
+ reply = data._srcHandler.getReply(600);
+ ASSERT_TRUE(reply.get() != NULL);
+ fprintf(stderr, "%s\n", reply->getTrace().toString().c_str());
+ EXPECT_TRUE(!reply->hasErrors());
+}
diff --git a/documentapi/src/tests/systemstate/.gitignore b/documentapi/src/tests/systemstate/.gitignore
new file mode 100644
index 00000000000..3f52bc38742
--- /dev/null
+++ b/documentapi/src/tests/systemstate/.gitignore
@@ -0,0 +1,4 @@
+.depend
+Makefile
+systemstate_test
+documentapi_systemstate_test_app
diff --git a/documentapi/src/tests/systemstate/CMakeLists.txt b/documentapi/src/tests/systemstate/CMakeLists.txt
new file mode 100644
index 00000000000..a1a88c66278
--- /dev/null
+++ b/documentapi/src/tests/systemstate/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(documentapi_systemstate_test_app
+ SOURCES
+ systemstate.cpp
+ DEPENDS
+ documentapi
+)
+vespa_add_test(NAME documentapi_systemstate_test_app COMMAND documentapi_systemstate_test_app)
diff --git a/documentapi/src/tests/systemstate/DESC b/documentapi/src/tests/systemstate/DESC
new file mode 100644
index 00000000000..19dbc9195f1
--- /dev/null
+++ b/documentapi/src/tests/systemstate/DESC
@@ -0,0 +1,3 @@
+This is a unit test for the system state parser and the corresponding NodeState class. It mirrors the
+StateParserTestCase available in the java implementation of message bus. It consists of tests that verify that parsing
+works, pathing works, encoding/decoding works, and finally that the NodeState class works as intended.
diff --git a/documentapi/src/tests/systemstate/FILES b/documentapi/src/tests/systemstate/FILES
new file mode 100644
index 00000000000..e1d0e026d31
--- /dev/null
+++ b/documentapi/src/tests/systemstate/FILES
@@ -0,0 +1 @@
+systemstate.cpp
diff --git a/documentapi/src/tests/systemstate/systemstate.cpp b/documentapi/src/tests/systemstate/systemstate.cpp
new file mode 100644
index 00000000000..b8f6c04fa2b
--- /dev/null
+++ b/documentapi/src/tests/systemstate/systemstate.cpp
@@ -0,0 +1,225 @@
+// 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 <vespa/log/log.h>
+LOG_SETUP("systemstate_test");
+
+#include <vespa/documentapi/messagebus/systemstate/systemstate.h>
+#include <vespa/documentapi/messagebus/systemstate/systemstatehandle.h>
+#include <vespa/vespalib/testkit/testapp.h>
+
+using namespace documentapi;
+
+class Test : public vespalib::TestApp {
+public:
+ int Main();
+ void testParser();
+ void testPathing();
+ void testState();
+ void testEncoding();
+ void testHandle();
+ void testCompact();
+
+private:
+ void assertParser(const string &state, const string &expected = "");
+};
+
+TEST_APPHOOK(Test);
+
+int
+Test::Main()
+{
+ TEST_INIT("systemstate_test");
+
+ testParser(); TEST_FLUSH();
+ testPathing(); TEST_FLUSH();
+ testState(); TEST_FLUSH();
+ testEncoding(); TEST_FLUSH();
+ testHandle(); TEST_FLUSH();
+ testCompact(); TEST_FLUSH();
+
+ TEST_DONE();
+ return 0;
+}
+
+void
+Test::testParser()
+{
+ assertParser("storage");
+ assertParser("storage?", "ERROR");
+ assertParser("storage?a", "ERROR");
+ assertParser("storage?a=", "ERROR");
+ assertParser("storage?a=1");
+ assertParser("storage?a=1&", "ERROR");
+ assertParser("storage?a=1&b", "ERROR");
+ assertParser("storage?a=1&b=2");
+ assertParser("storage?a=1&b=2 search");
+ assertParser("storage?a=1&b=2 search?", "ERROR");
+ assertParser("storage?a=1&b=2 search?a", "ERROR");
+ assertParser("storage?a=1&b=2 search?a=", "ERROR");
+ assertParser("storage?a=1&b=2 search?a=1");
+ assertParser("storage?a=1&b=2 search?a=1&", "ERROR");
+ assertParser("storage?a=1&b=2 search?a=1&b", "ERROR");
+ assertParser("storage?a=1&b=2 search?a=1&b=", "ERROR");
+ assertParser("storage?a=1&b=2 search?a=1&b=2");
+
+ assertParser("storage");
+ assertParser("storage/");
+ assertParser("storage/?", "ERROR");
+ assertParser("storage/?a", "ERROR");
+ assertParser("storage/?a=", "ERROR");
+ assertParser("storage/?a=1");
+ assertParser("storage/cluster.storage");
+ assertParser("storage/cluster.storage/");
+
+ assertParser("storage?a=1");
+ assertParser("storage/?a=1");
+ assertParser("storage/.?a=1");
+ assertParser("storage/./?a=1");
+ assertParser("storage/./cluster.storage?a=1");
+ assertParser("storage/./cluster.storage/?a=1");
+ assertParser("storage/./cluster.storage/..?a=1");
+ assertParser("storage/./cluster.storage/../?a=1");
+ assertParser("storage/./cluster.storage/../storage?a=1");
+ assertParser("storage/./cluster.storage/../storage/?a=1");
+}
+
+void
+Test::testPathing()
+{
+ assertParser("storage?a=1", "storage?a=1");
+ assertParser("storage/?a=1", "storage?a=1");
+ assertParser("storage/.?a=1", "storage?a=1");
+ assertParser("storage/./?a=1", "storage?a=1");
+ assertParser("storage/./cluster.storage?a=1", "storage/cluster.storage?a=1");
+ assertParser("storage/./cluster.storage/?a=1", "storage/cluster.storage?a=1");
+ assertParser("storage/./cluster.storage/..?a=1", "storage?a=1");
+ assertParser("storage/./cluster.storage/../?a=1", "storage?a=1");
+ assertParser("storage/./cluster.storage/../storage?a=1", "storage/storage?a=1");
+ assertParser("storage/./cluster.storage/../storage/?a=1", "storage/storage?a=1");
+
+ assertParser("a?p1=1 a/b?p2=2 a/b/c?p3=3", "a?p1=1 a/b?p2=2 a/b/c?p3=3");
+ assertParser("a .?p1=1 ./b?p2=2 ./b/c?p3=3", "a?p1=1 a/b?p2=2 a/b/c?p3=3");
+ assertParser("a .?p1=1 ./../a/b/ .?p2=2 c?p3=3", "a?p1=1 a/b?p2=2 a/b/c?p3=3");
+ assertParser("a/./ .?p1=1 ../a/b/c/.. .?p2=2 ./c/../c?p3=3", "a?p1=1 a/b?p2=2 a/b/c?p3=3");
+ assertParser("a/b/c/d/ ../../ ../ ../a .?p1=1 ./b?p2=2 ./ ../a/b/c?p3=3", "a?p1=1 a/b?p2=2 a/b/c?p3=3");
+
+ assertParser("a/b/c/d?p1=1 a?p2=2", "a?p2=2 a/b/c/d?p1=1");
+ assertParser("a/b/c/d/?p1=1 /a?p2=2", "a?p2=2 a/b/c/d?p1=1");
+ assertParser("/a/b/c/d/?p1=1 /a?p2=2", "a?p2=2 a/b/c/d?p1=1");
+
+ assertParser("a .?p1=1", "a?p1=1");
+ assertParser("a/b .?p1=1", "a/b?p1=1");
+ assertParser("a/b c?p1=1 d?p2=2", "a/b/c?p1=1 a/b/d?p2=2");
+}
+
+void
+Test::testState()
+{
+ NodeState state;
+ state
+ .addChild("distributor", NodeState()
+ .setState("n", "27"))
+ .addChild("storage", NodeState()
+ .setState("n", "170")
+ .addChild("2", NodeState()
+ .setState("s", "d"))
+ .addChild("13", NodeState()
+ .setState("s", "r")
+ .setState("c", "0.0")));
+
+ EXPECT_EQUAL("27", state.getState("distributor/n"));
+ EXPECT_EQUAL("170", state.getState("storage/n"));
+ EXPECT_EQUAL("d", state.getState("storage/2/s"));
+ EXPECT_EQUAL("r", state.getState("storage/13/s"));
+ EXPECT_EQUAL("0.0", state.getState("storage/13/c"));
+
+ EXPECT_EQUAL("27", state.getChild("distributor")->getState("n"));
+ EXPECT_EQUAL("170", state.getChild("storage")->getState("n"));
+ EXPECT_EQUAL("d", state.getChild("storage")->getChild("2")->getState("s"));
+ EXPECT_EQUAL("r", state.getChild("storage")->getChild("13")->getState("s"));
+ EXPECT_EQUAL("0.0", state.getChild("storage")->getChild("13")->getState("c"));
+}
+
+void
+Test::testEncoding()
+{
+ NodeState state;
+ state.setState("foo", "http://search.yahoo.com/?query=bar");
+ LOG(info, "'%s'", state.toString().c_str());
+ EXPECT_EQUAL(".?foo=http%3A%2F%2Fsearch.yahoo.com%2F%3Fquery%3Dbar", state.toString());
+ assertParser(state.toString(), state.toString());
+
+ state = NodeState()
+ .addChild("foo:bar", NodeState()
+ .setState("foo", "http://search.yahoo.com/?query=bar"));
+ LOG(info, "'%s'", state.toString().c_str());
+ EXPECT_EQUAL("foo%3Abar?foo=http%3A%2F%2Fsearch.yahoo.com%2F%3Fquery%3Dbar", state.toString());
+ assertParser(state.toString(), state.toString());
+
+ state = NodeState()
+ .addChild("foo/bar", NodeState()
+ .setState("foo", "http://search.yahoo.com/?query=bar"));
+ LOG(info, "'%s'", state.toString().c_str());
+ EXPECT_EQUAL("foo/bar?foo=http%3A%2F%2Fsearch.yahoo.com%2F%3Fquery%3Dbar", state.toString());
+ assertParser(state.toString(), state.toString());
+}
+
+void
+Test::testHandle()
+{
+ SystemState::UP state(SystemState::newInstance(""));
+ ASSERT_TRUE(state.get() != NULL);
+
+ SystemStateHandle handle(*state);
+ ASSERT_TRUE(handle.isValid());
+
+ SystemStateHandle hoe(handle);
+ ASSERT_TRUE(!handle.isValid());
+ ASSERT_TRUE(hoe.isValid());
+}
+
+void
+Test::testCompact()
+{
+ NodeState state;
+ state
+ .setState("a/b0/s", "d")
+ .setState("a/b0/c0/s", "d")
+ .setState("a/b0/c1/s", "d")
+ .setState("a/b1/s", "d")
+ .setState("a/b1/c0/s", "d")
+ .setState("a/b1/c1/s", "d");
+ EXPECT_EQUAL("a/b0?s=d a/b0/c0?s=d a/b0/c1?s=d a/b1?s=d a/b1/c0?s=d a/b1/c1?s=d", state.toString());
+
+ state.removeChild("a/b0/c0");
+ EXPECT_EQUAL("a/b0?s=d a/b0/c1?s=d a/b1?s=d a/b1/c0?s=d a/b1/c1?s=d", state.toString());
+
+ state.removeState("a/b0/c1/s");
+ EXPECT_EQUAL("a/b0?s=d a/b1?s=d a/b1/c0?s=d a/b1/c1?s=d", state.toString());
+
+ state.setState("a/b1/c0/s", "");
+ EXPECT_EQUAL("a/b0?s=d a/b1?s=d a/b1/c1?s=d", state.toString());
+
+ state.removeChild("a/b1");
+ EXPECT_EQUAL("a/b0?s=d", state.toString());
+
+ state.removeChild("a");
+ EXPECT_EQUAL("", state.toString());
+}
+
+void
+Test::assertParser(const string &state, const string &expected)
+{
+ SystemState::UP obj = SystemState::newInstance(state);
+ if (obj.get() == NULL) {
+ EXPECT_EQUAL("ERROR", expected);
+ }
+ else {
+ SystemStateHandle handle(*obj);
+ LOG(info, "'%s' => '%s'", state.c_str(), handle.getRoot().toString().c_str());
+ if (!expected.empty()) {
+ EXPECT_EQUAL(expected, handle.getRoot().toString());
+ }
+ }
+}
+