diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /documentapi/src/tests |
Publish
Diffstat (limited to 'documentapi/src/tests')
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 ¶m, + const string &pattern = "", int32_t numEntries = -1); + bool isErrorPolicy(const string &name, const string ¶m); + 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 ¶m, + 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 ¶m) +{ + 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 ¶ms) : + 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 ¶m); + void select(mbus::RoutingContext &ctx); + void merge(mbus::RoutingContext &ctx); +}; + +MyPolicy::MyPolicy(const string ¶m) : + _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 ¶m) const; +}; + +mbus::IRoutingPolicy::UP +MyFactory::createPolicy(const string ¶m) 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()); + } + } +} + |