diff options
author | Henning Baldersheim <balder@yahoo-inc.com> | 2022-05-18 11:05:54 +0000 |
---|---|---|
committer | Henning Baldersheim <balder@yahoo-inc.com> | 2022-05-18 11:05:54 +0000 |
commit | 39443ba7ffe7966fb06555ef832f4eff3756c076 (patch) | |
tree | 5e0a2fd6ab79aa6be435551ea307be9750e69227 /vespalib/src/tests | |
parent | 36df8bd3d9fd4ee60aadd04af89199a8bc504e68 (diff) |
Move state_server, metrivs and some all executors from staging_vespalib too vespalib.
Diffstat (limited to 'vespalib/src/tests')
25 files changed, 2678 insertions, 2 deletions
diff --git a/vespalib/src/tests/encoding/.gitignore b/vespalib/src/tests/encoding/.gitignore new file mode 100644 index 00000000000..a3e9c375723 --- /dev/null +++ b/vespalib/src/tests/encoding/.gitignore @@ -0,0 +1,3 @@ +.depend +Makefile +*_test diff --git a/vespalib/src/tests/encoding/base64/.gitignore b/vespalib/src/tests/encoding/base64/.gitignore new file mode 100644 index 00000000000..bd63ed7e5cb --- /dev/null +++ b/vespalib/src/tests/encoding/base64/.gitignore @@ -0,0 +1 @@ +vespalib_base64_test_app diff --git a/vespalib/src/tests/encoding/base64/CMakeLists.txt b/vespalib/src/tests/encoding/base64/CMakeLists.txt new file mode 100644 index 00000000000..e2bb5d83fbe --- /dev/null +++ b/vespalib/src/tests/encoding/base64/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vespalib_base64_test_app TEST + SOURCES + base64_test.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_base64_test_app COMMAND vespalib_base64_test_app) diff --git a/vespalib/src/tests/encoding/base64/base64_test.cpp b/vespalib/src/tests/encoding/base64/base64_test.cpp new file mode 100644 index 00000000000..295aad7ffdd --- /dev/null +++ b/vespalib/src/tests/encoding/base64/base64_test.cpp @@ -0,0 +1,83 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/encoding/base64.h> +#include <vector> + +using namespace vespalib; + +TEST_SETUP(Test); + +int +Test::Main() +{ + TEST_INIT("base64_test"); + + // Basic test without padding + std::string source = "No need to pad this string."; + std::string encoded = Base64::encode(source); + std::string expected = "Tm8gbmVlZCB0byBwYWQgdGhpcyBzdHJpbmcu"; + std::string decoded = Base64::decode(encoded); + + EXPECT_EQUAL(expected, encoded); + EXPECT_EQUAL(source, decoded); + + EXPECT_TRUE(static_cast<uint32_t>( + Base64::getMaximumEncodeLength(source.size())) >= encoded.size()); + EXPECT_TRUE(static_cast<uint32_t>( + Base64::getMaximumDecodeLength(encoded.size())) >= source.size()); + + // Basic string that needs padding + source = "This string will need to be padded."; + encoded = Base64::encode(source); + expected = "VGhpcyBzdHJpbmcgd2lsbCBuZWVkIHRvIGJlIHBhZGRlZC4="; + decoded = Base64::decode(encoded); + + EXPECT_EQUAL(expected, encoded); + EXPECT_EQUAL(source, decoded); + + EXPECT_TRUE(static_cast<uint32_t>( + Base64::getMaximumEncodeLength(source.size())) >= encoded.size()); + EXPECT_TRUE(static_cast<uint32_t>( + Base64::getMaximumDecodeLength(encoded.size())) >= source.size()); + + // Check that max sizes are good for whatever input sizes + source = ""; + for (uint32_t i=0; i<100; ++i) { + source += "a"; + // Code will assert if -1 is returned from either + // getMaximumEncodeLength() or getMaximumDecodeLength(). + encoded = Base64::encode(source); + decoded = Base64::decode(encoded); + EXPECT_EQUAL(source, decoded); + } + + // Check that -1 is returned on too little space when encoding + source = "Checking that -1 is returned when not enough space to encode"; + std::vector<char> buffer(100, '\0'); + uint32_t minSizeNeeded = 81; + for (uint32_t i=0; i<minSizeNeeded; ++i) { + EXPECT_EQUAL(-1, Base64::encode(source.c_str(), source.size(), + &buffer[0], i)); + } + EXPECT_EQUAL(80, Base64::encode(source.c_str(), source.size(), + &buffer[0], minSizeNeeded)); + EXPECT_EQUAL(Base64::encode(source), std::string(&buffer[0], 80)); + EXPECT_TRUE(minSizeNeeded <= static_cast<uint32_t>( + Base64::getMaximumEncodeLength(source.size()))); + + EXPECT_TRUE(buffer[80] == '\0'); + + // Check that -1 is returned on too little space when decoding + encoded = Base64::encode(source); + minSizeNeeded = 60; + for (uint32_t i=0; i<minSizeNeeded; ++i) { + EXPECT_EQUAL(-1, Base64::decode(encoded.c_str(), encoded.size(), + &buffer[0], i)); + } + EXPECT_EQUAL(60, Base64::decode(encoded.c_str(), encoded.size(), + &buffer[0], minSizeNeeded)); + EXPECT_EQUAL(source, std::string(&buffer[0], 60)); + + TEST_DONE(); +} diff --git a/vespalib/src/tests/fileheader/.gitignore b/vespalib/src/tests/fileheader/.gitignore new file mode 100644 index 00000000000..f41a6844d34 --- /dev/null +++ b/vespalib/src/tests/fileheader/.gitignore @@ -0,0 +1,6 @@ +*.So +.depend* +Makefile +fileheader.tmp +fileheader_test +vespalib_fileheader_test_app diff --git a/vespalib/src/tests/fileheader/CMakeLists.txt b/vespalib/src/tests/fileheader/CMakeLists.txt new file mode 100644 index 00000000000..a58507e818e --- /dev/null +++ b/vespalib/src/tests/fileheader/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vespalib_fileheader_test_app TEST + SOURCES + fileheader_test.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_fileheader_test_app COMMAND vespalib_fileheader_test_app) diff --git a/vespalib/src/tests/fileheader/fileheader.dat b/vespalib/src/tests/fileheader/fileheader.dat Binary files differnew file mode 100644 index 00000000000..90660f64b98 --- /dev/null +++ b/vespalib/src/tests/fileheader/fileheader.dat diff --git a/vespalib/src/tests/fileheader/fileheader_test.cpp b/vespalib/src/tests/fileheader/fileheader_test.cpp new file mode 100644 index 00000000000..21e374e4f62 --- /dev/null +++ b/vespalib/src/tests/fileheader/fileheader_test.cpp @@ -0,0 +1,694 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/data/fileheader.h> +#include <vespa/vespalib/data/databuffer.h> +#include <vespa/fastos/file.h> + +using namespace vespalib; + +class Test : public vespalib::TestApp { +private: + void testTag(); + void testTagErrors(); + void testTagIteration(); + void testGenericHeader(); + void testBufferReader(); + void testBufferWriter(); + void testBufferAccess(); + void testFileReader(); + void testFileWriter(); + void testFileHeader(); + void testFileAlign(); + void testFileSize(); + void testReadErrors(); + bool testReadError(DataBuffer &buf, const std::string &expected); + void testWriteErrors(); + void testRewriteErrors(); + void testLayout(); + + void testReadSize(bool mapped); + void testReadSizeErrors(bool mapped); + bool testReadSizeError(DataBuffer &buf, const std::string &expected, bool mapped); + +public: + int Main() override { + TEST_INIT("fileheader_test"); + + testTag(); TEST_FLUSH(); + testTagErrors(); TEST_FLUSH(); + testTagIteration(); TEST_FLUSH(); + testGenericHeader(); TEST_FLUSH(); + testBufferReader(); TEST_FLUSH(); + testBufferWriter(); TEST_FLUSH(); + testBufferAccess(); TEST_FLUSH(); + testFileReader(); TEST_FLUSH(); + testFileWriter(); TEST_FLUSH(); + testFileHeader(); TEST_FLUSH(); + testFileAlign(); TEST_FLUSH(); + testFileSize(); TEST_FLUSH(); + testReadErrors(); TEST_FLUSH(); + testWriteErrors(); TEST_FLUSH(); + testRewriteErrors(); TEST_FLUSH(); + testLayout(); TEST_FLUSH(); + testReadSize(false); TEST_FLUSH(); + testReadSizeErrors(false); TEST_FLUSH(); + testReadSize(true); TEST_FLUSH(); + testReadSizeErrors(true); TEST_FLUSH(); + + TEST_DONE(); + } +}; + +TEST_APPHOOK(Test); + +void +Test::testTag() +{ + { + std::vector<GenericHeader::Tag> tags; + tags.push_back(GenericHeader::Tag("foo", 6.9)); + tags.push_back(GenericHeader::Tag("foo", 6.9f)); + for (std::vector<GenericHeader::Tag>::iterator it = tags.begin(); + it != tags.end(); ++it) + { + GenericHeader::Tag tag = *it; + for (uint32_t i = 0; i < 2; ++i) { + EXPECT_EQUAL(GenericHeader::Tag::TYPE_FLOAT, tag.getType()); + EXPECT_EQUAL("foo", tag.getName()); + EXPECT_TRUE(tag.asString().empty()); + EXPECT_APPROX(6.9, tag.asFloat(), 1E-6); + EXPECT_EQUAL(0, tag.asInteger()); + + uint32_t len = tag.getSize(); + DataBuffer buf(len); + EXPECT_EQUAL(len, tag.write(buf)); + + GenericHeader::Tag tmp; + EXPECT_EQUAL(len, tmp.read(buf)); + tag = tmp; + } + } + } + { + std::vector<GenericHeader::Tag> tags; + tags.push_back(GenericHeader::Tag("foo", (int8_t)69)); + tags.push_back(GenericHeader::Tag("foo", (uint8_t)69)); + tags.push_back(GenericHeader::Tag("foo", (int16_t)69)); + tags.push_back(GenericHeader::Tag("foo", (uint16_t)69)); + tags.push_back(GenericHeader::Tag("foo", (int32_t)69)); + tags.push_back(GenericHeader::Tag("foo", (uint32_t)69)); + tags.push_back(GenericHeader::Tag("foo", (int64_t)69)); + for (std::vector<GenericHeader::Tag>::iterator it = tags.begin(); + it != tags.end(); ++it) + { + GenericHeader::Tag tag = *it; + for (uint32_t i = 0; i < 2; ++i) { + EXPECT_EQUAL(GenericHeader::Tag::TYPE_INTEGER, tag.getType()); + EXPECT_EQUAL("foo", tag.getName()); + EXPECT_TRUE(tag.asString().empty()); + EXPECT_EQUAL(0.0, tag.asFloat()); + EXPECT_EQUAL(69l, tag.asInteger()); + + uint32_t len = tag.getSize(); + DataBuffer buf(len); + EXPECT_EQUAL(len, tag.write(buf)); + + GenericHeader::Tag tmp; + EXPECT_EQUAL(len, tmp.read(buf)); + tag = tmp; + } + } + } + { + GenericHeader::Tag tag("foo", "bar"); + for (uint32_t i = 0; i < 2; ++i) { + EXPECT_EQUAL(GenericHeader::Tag::TYPE_STRING, tag.getType()); + EXPECT_EQUAL("foo", tag.getName()); + EXPECT_EQUAL("bar", tag.asString()); + EXPECT_EQUAL(0.0, tag.asFloat()); + EXPECT_EQUAL(0, tag.asInteger()); + + uint32_t len = tag.getSize(); + DataBuffer buf(len); + EXPECT_EQUAL(len, tag.write(buf)); + + GenericHeader::Tag tmp; + EXPECT_EQUAL(len, tmp.read(buf)); + tag = tmp; + } + } + { + GenericHeader::Tag trueTag("foo", true); + GenericHeader::Tag falseTag("foo", false); + EXPECT_EQUAL(GenericHeader::Tag::TYPE_INTEGER, trueTag.getType()); + EXPECT_EQUAL(GenericHeader::Tag::TYPE_INTEGER, falseTag.getType()); + EXPECT_EQUAL(1, trueTag.asInteger()); + EXPECT_EQUAL(0, falseTag.asInteger()); + EXPECT_TRUE(trueTag.asBool()); + EXPECT_FALSE(falseTag.asBool()); + } +} + +void +Test::testTagErrors() +{ + DataBuffer buf(1024); + buf.writeBytes("foo", 3); + buf.writeInt8(0); + buf.writeInt8((uint8_t)GenericHeader::Tag::TYPE_EMPTY); + + GenericHeader::Tag tag("bar", 6.9); + try { + tag.read(buf); + EXPECT_TRUE(false); + } catch (IllegalHeaderException &e) { + EXPECT_EQUAL("Can not deserialize empty tag.", e.getMessage()); + } + EXPECT_EQUAL("bar", tag.getName()); + EXPECT_EQUAL(GenericHeader::Tag::TYPE_FLOAT, tag.getType()); + EXPECT_EQUAL(6.9, tag.asFloat()); +} + +void +Test::testTagIteration() +{ + GenericHeader header; + header.putTag(GenericHeader::Tag("foo", 6.9)); + header.putTag(GenericHeader::Tag("bar", 6699)); + header.putTag(GenericHeader::Tag("baz", "666999")); + + EXPECT_EQUAL(3u, header.getNumTags()); + EXPECT_EQUAL("bar", header.getTag(0).getName()); + EXPECT_EQUAL("baz", header.getTag(1).getName()); + EXPECT_EQUAL("foo", header.getTag(2).getName()); +} + +void +Test::testGenericHeader() +{ + GenericHeader header; + EXPECT_TRUE(header.isEmpty()); + EXPECT_EQUAL(0u, header.getNumTags()); + EXPECT_TRUE(!header.hasTag("foo")); + EXPECT_TRUE(header.getTag("foo").isEmpty()); + EXPECT_TRUE(!header.hasTag("bar")); + EXPECT_TRUE(header.getTag("bar").isEmpty()); + EXPECT_TRUE(!header.hasTag("baz")); + EXPECT_TRUE(header.getTag("baz").isEmpty()); + + header.putTag(GenericHeader::Tag("foo", 6.9)); + EXPECT_TRUE(!header.isEmpty()); + EXPECT_EQUAL(1u, header.getNumTags()); + EXPECT_TRUE(header.hasTag("foo")); + EXPECT_EQUAL(6.9, header.getTag("foo").asFloat()); + EXPECT_TRUE(!header.hasTag("bar")); + EXPECT_TRUE(header.getTag("bar").isEmpty()); + EXPECT_TRUE(!header.hasTag("baz")); + EXPECT_TRUE(header.getTag("baz").isEmpty()); + + header.putTag(GenericHeader::Tag("bar", 6699)); + EXPECT_TRUE(!header.isEmpty()); + EXPECT_EQUAL(2u, header.getNumTags()); + EXPECT_TRUE(header.hasTag("foo")); + EXPECT_EQUAL(6.9, header.getTag("foo").asFloat()); + EXPECT_TRUE(header.hasTag("bar")); + EXPECT_EQUAL(6699, header.getTag("bar").asInteger()); + EXPECT_TRUE(!header.hasTag("baz")); + EXPECT_TRUE(header.getTag("baz").isEmpty()); + + header.putTag(GenericHeader::Tag("baz", "666999")); + EXPECT_TRUE(header.hasTag("foo")); + EXPECT_EQUAL(6.9, header.getTag("foo").asFloat()); + EXPECT_TRUE(header.hasTag("bar")); + EXPECT_EQUAL(6699, header.getTag("bar").asInteger()); + EXPECT_TRUE(header.hasTag("baz")); + EXPECT_EQUAL("666999", header.getTag("baz").asString()); + + header.removeTag("bar"); + EXPECT_TRUE(header.hasTag("foo")); + EXPECT_EQUAL(6.9, header.getTag("foo").asFloat()); + EXPECT_TRUE(!header.hasTag("bar")); + EXPECT_TRUE(header.getTag("bar").isEmpty()); + EXPECT_TRUE(header.hasTag("baz")); + EXPECT_EQUAL("666999", header.getTag("baz").asString()); + + header.removeTag("foo"); + EXPECT_TRUE(!header.hasTag("foo")); + EXPECT_TRUE(header.getTag("foo").isEmpty()); + EXPECT_TRUE(!header.hasTag("bar")); + EXPECT_TRUE(header.getTag("bar").isEmpty()); + EXPECT_TRUE(header.hasTag("baz")); + EXPECT_EQUAL("666999", header.getTag("baz").asString()); + + header.removeTag("baz"); + EXPECT_TRUE(!header.hasTag("foo")); + EXPECT_TRUE(header.getTag("foo").isEmpty()); + EXPECT_TRUE(!header.hasTag("bar")); + EXPECT_TRUE(header.getTag("bar").isEmpty()); + EXPECT_TRUE(!header.hasTag("baz")); + EXPECT_TRUE(header.getTag("baz").isEmpty()); +} + +void +Test::testBufferReader() +{ + DataBuffer src(256); + for (uint32_t i = 0; i < 256; ++i) { + src.writeInt8((uint8_t)i); + } + + GenericHeader::BufferReader reader(src); + + char dst[7]; + uint32_t sum = 0; + while (sum < 256) { + uint32_t len = (uint32_t)reader.getData(dst, 7); + for (uint32_t i = 0; i < len; ++i) { + EXPECT_EQUAL(sum + i, (uint8_t)dst[i]); + } + sum += len; + } + EXPECT_EQUAL(256u, sum); +} + +void +Test::testBufferWriter() +{ + DataBuffer dst(256); + GenericHeader::BufferWriter writer(dst); + + uint32_t sum = 0; + while(sum < 256) { + char src[7]; + for (uint32_t i = 0; i < 7; ++i) { + src[i] = (uint8_t)(sum + i); + } + uint32_t len = std::min(7u, 256 - sum); + EXPECT_EQUAL(len, (uint32_t)writer.putData(src, len)); + sum += len; + } + EXPECT_EQUAL(256u, sum); + + // flip dst + for (uint32_t i = 0; i < 256; ++i) { + uint8_t b = dst.readInt8(); + EXPECT_EQUAL(i, (uint32_t)b); + } +} + +void +Test::testBufferAccess() +{ + DataBuffer buf; + uint32_t len; + { + GenericHeader header; + header.putTag(GenericHeader::Tag("foo", 6.9)); + header.putTag(GenericHeader::Tag("bar", 6699)); + header.putTag(GenericHeader::Tag("baz", "666999")); + + int64_t bval = 0x1234567890abcdefLL; + header.putTag(GenericHeader::Tag("big", bval)); + + len = header.getSize(); + buf.ensureFree(len); + GenericHeader::BufferWriter writer(buf); + EXPECT_EQUAL(len, header.write(writer)); + } + { + GenericHeader header; + GenericHeader::BufferReader reader(buf); + EXPECT_EQUAL(len, header.read(reader)); + + EXPECT_TRUE(header.hasTag("foo")); + EXPECT_EQUAL(6.9, header.getTag("foo").asFloat()); + EXPECT_TRUE(header.hasTag("bar")); + EXPECT_EQUAL(6699, header.getTag("bar").asInteger()); + EXPECT_TRUE(header.hasTag("baz")); + EXPECT_EQUAL("666999", header.getTag("baz").asString()); + EXPECT_TRUE(header.hasTag("big")); + EXPECT_EQUAL(0x1234567890abcdefLL, header.getTag("big").asInteger()); + } +} + +void +Test::testFileReader() +{ + { + FastOS_File file; + ASSERT_TRUE(file.OpenWriteOnlyTruncate("fileheader.tmp")); + + uint8_t buf[256]; + for (uint32_t i = 0; i < 256; ++i) { + buf[i] = (uint8_t)i; + } + EXPECT_EQUAL(256, file.Write2(buf, 256)); + } + { + FastOS_File file; + ASSERT_TRUE(file.OpenReadOnly("fileheader.tmp")); + FileHeader::FileReader reader(file); + + char buf[7]; + uint32_t sum = 0; + while(sum < 256) { + uint32_t len = (uint32_t)reader.getData(buf, 7); + for (uint32_t i = 0; i < len; ++i) { + EXPECT_EQUAL(sum + i, (uint8_t)buf[i]); + } + sum += len; + } + EXPECT_EQUAL(256u, sum); + + ASSERT_TRUE(file.Close()); + file.Delete(); + } +} + +void +Test::testFileWriter() +{ + { + FastOS_File file; + ASSERT_TRUE(file.OpenWriteOnlyTruncate("fileheader.tmp")); + FileHeader::FileWriter writer(file); + + uint32_t sum = 0; + while(sum < 256) { + char src[7]; + for (uint32_t i = 0; i < 7; ++i) { + src[i] = (uint8_t)(sum + i); + } + uint32_t len = std::min(7u, 256 - sum); + EXPECT_EQUAL(len, (uint32_t)writer.putData(src, len)); + sum += len; + } + EXPECT_EQUAL(256u, sum); + } + { + FastOS_File file; + ASSERT_TRUE(file.OpenReadOnly("fileheader.tmp")); + + uint8_t buf[256]; + EXPECT_EQUAL(256, file.Read(buf, 256)); + for (uint32_t i = 0; i < 256; ++i) { + EXPECT_EQUAL(i, (uint32_t)buf[i]); + } + + ASSERT_TRUE(file.Close()); + file.Delete(); + } +} + +void +Test::testFileHeader() +{ + uint32_t len = 0; + { + FileHeader header; + header.putTag(FileHeader::Tag("foo", 6.9)); + header.putTag(FileHeader::Tag("bar", 6699)); + header.putTag(FileHeader::Tag("baz", "666999")); + + FastOS_File file; + ASSERT_TRUE(file.OpenWriteOnlyTruncate("fileheader.tmp")); + len = header.writeFile(file); + EXPECT_EQUAL(len, header.getSize()); + } + { + FastOS_File file; + ASSERT_TRUE(file.OpenReadWrite("fileheader.tmp")); + + FileHeader header; + EXPECT_EQUAL(len, header.readFile(file)); + EXPECT_EQUAL(len, header.getSize()); + + EXPECT_TRUE(header.hasTag("foo")); + EXPECT_EQUAL(6.9, header.getTag("foo").asFloat()); + EXPECT_TRUE(header.hasTag("bar")); + EXPECT_EQUAL(6699, header.getTag("bar").asInteger()); + EXPECT_TRUE(header.hasTag("baz")); + EXPECT_EQUAL("666999", header.getTag("baz").asString()); + + header.putTag(FileHeader::Tag("foo", 9.6)); + header.putTag(FileHeader::Tag("bar", 9966)); + header.putTag(FileHeader::Tag("baz", "999666")); + EXPECT_EQUAL(len, header.getSize()); + EXPECT_EQUAL(len, header.rewriteFile(file)); + } + { + FileHeader header; + + FastOS_File file; + ASSERT_TRUE(file.OpenReadOnly("fileheader.tmp")); + EXPECT_EQUAL(len, header.readFile(file)); + EXPECT_EQUAL(len, header.getSize()); + ASSERT_TRUE(file.Close()); + file.Delete(); + + EXPECT_TRUE(header.hasTag("foo")); + EXPECT_EQUAL(9.6, header.getTag("foo").asFloat()); + EXPECT_TRUE(header.hasTag("bar")); + EXPECT_EQUAL(9966, header.getTag("bar").asInteger()); + EXPECT_TRUE(header.hasTag("baz")); + EXPECT_EQUAL("999666", header.getTag("baz").asString()); + } +} + +void +Test::testFileAlign() +{ + for (uint32_t alignTo = 1; alignTo < 16; ++alignTo) { + FileHeader header(alignTo); + header.putTag(FileHeader::Tag("foo", "bar")); + EXPECT_EQUAL(0u, header.getSize() % alignTo); + } +} + +void +Test::testFileSize() +{ + for (uint32_t minSize = 0; minSize < 512; ++minSize) { + FileHeader header(1u, minSize); + header.putTag(FileHeader::Tag("foo", "bar")); + EXPECT_TRUE(header.getSize() >= minSize); + } +} + +void +Test::testReadErrors() +{ + { + DataBuffer buf; + EXPECT_TRUE(testReadError(buf, "Failed to read header info.")); + } + { + DataBuffer buf; + buf.writeInt32(0xDEADBEAF); + buf.writeInt32(8); + EXPECT_TRUE(testReadError(buf, "Failed to verify magic bits.")); + } + { + DataBuffer buf; + buf.writeInt32(GenericHeader::MAGIC); + buf.writeInt32(8); + EXPECT_TRUE(testReadError(buf, "Failed to verify header size.")); + } + { + DataBuffer buf; + buf.writeInt32(GenericHeader::MAGIC); + buf.writeInt32(16); + buf.writeInt32(-1); + buf.writeInt32(0); + EXPECT_TRUE(testReadError(buf, "Failed to verify header version.")); + } + { + DataBuffer buf; + buf.writeInt32(GenericHeader::MAGIC); + buf.writeInt32(21); + buf.writeInt32(GenericHeader::VERSION); + buf.writeInt32(1); + buf.writeBytes("foo", 3); + buf.writeInt8(0); + buf.writeInt8((uint8_t)GenericHeader::Tag::TYPE_EMPTY); + EXPECT_TRUE(testReadError(buf, "Can not deserialize empty tag.")); + } +} + +bool +Test::testReadError(DataBuffer &buf, const std::string &expected) +{ + GenericHeader header; + header.putTag(GenericHeader::Tag("foo", "bar")); + try { + GenericHeader::BufferReader reader(buf); + header.read(reader); + EXPECT_TRUE(false); + return false; + } catch (IllegalHeaderException &e) { + if (!EXPECT_EQUAL(expected, e.getMessage())) { + return false; + } + } + if (!EXPECT_EQUAL(1u, header.getNumTags())) { + return false; + } + if (!EXPECT_EQUAL("bar", header.getTag("foo").asString())) { + return false; + } + return true; +} + +void +Test::testWriteErrors() +{ + GenericHeader header; + header.putTag(GenericHeader::Tag("foo", 69)); + + DataBuffer buf; + buf.ensureFree(4); + buf.moveFreeToData(buf.getFreeLen() - 4); + EXPECT_TRUE(header.getSize() > buf.getFreeLen()); + try { + GenericHeader::BufferWriter writer(buf); + header.write(writer); + EXPECT_TRUE(false); + } catch (IllegalHeaderException &e) { + EXPECT_EQUAL("Failed to write header.", e.getMessage()); + } + + EXPECT_TRUE(header.hasTag("foo")); + EXPECT_EQUAL(69, header.getTag("foo").asInteger()); +} + +void +Test::testRewriteErrors() +{ + FileHeader header; + header.putTag(FileHeader::Tag("foo", "bar")); + uint32_t len = header.getSize(); + + { + FastOS_File file; + ASSERT_TRUE(file.OpenWriteOnlyTruncate("fileheader.tmp")); + EXPECT_EQUAL(len, header.writeFile(file)); + } + { + FastOS_File file; + ASSERT_TRUE(file.OpenReadWrite("fileheader.tmp")); + header.putTag(FileHeader::Tag("baz", "cox")); + EXPECT_TRUE(len != header.getSize()); + try { + header.rewriteFile(file); + EXPECT_TRUE(false); + } catch (IllegalHeaderException &e) { + EXPECT_EQUAL("Failed to rewrite resized header.", e.getMessage()); + } + } +} + +void +Test::testLayout() +{ + FileHeader header; + { + FastOS_File file; + const std::string fileName = TEST_PATH("fileheader.dat"); + ASSERT_TRUE(file.OpenReadOnly(fileName.c_str())); + uint32_t len = header.readFile(file); + EXPECT_EQUAL(len, header.getSize()); + } + + EXPECT_TRUE(header.hasTag("foo")); + EXPECT_EQUAL(6.9, header.getTag("foo").asFloat()); + EXPECT_TRUE(header.hasTag("bar")); + EXPECT_EQUAL(6699, header.getTag("bar").asInteger()); + EXPECT_TRUE(header.hasTag("baz")); + EXPECT_EQUAL("666999", header.getTag("baz").asString()); +} + + +void +Test::testReadSize(bool mapped) +{ + DataBuffer buf; + buf.writeInt32(GenericHeader::MAGIC); + buf.writeInt32(21); + buf.writeInt32(GenericHeader::VERSION); + buf.writeInt32(1); + uint32_t headerLen; + if (mapped) { + GenericHeader::MMapReader reader(buf.getData(), buf.getDataLen()); + headerLen = FileHeader::readSize(reader); + } else { + GenericHeader::BufferReader reader(buf); + headerLen = FileHeader::readSize(reader); + } + EXPECT_EQUAL(21u, headerLen); +} + + +void +Test::testReadSizeErrors(bool mapped) +{ + { + DataBuffer buf; + EXPECT_TRUE(testReadSizeError(buf, "Failed to read header info.", + mapped)); + } + { + DataBuffer buf; + buf.writeInt32(0xDEADBEAF); + buf.writeInt32(8); + buf.writeInt32(0); + buf.writeInt32(0); + EXPECT_TRUE(testReadSizeError(buf, "Failed to verify magic bits.", + mapped)); + } + { + DataBuffer buf; + buf.writeInt32(GenericHeader::MAGIC); + buf.writeInt32(8); + buf.writeInt32(GenericHeader::VERSION); + buf.writeInt32(0); + EXPECT_TRUE(testReadSizeError(buf, "Failed to verify header size.", + mapped)); + } + { + DataBuffer buf; + buf.writeInt32(GenericHeader::MAGIC); + buf.writeInt32(16); + buf.writeInt32(-1); + buf.writeInt32(0); + EXPECT_TRUE(testReadSizeError(buf, + "Failed to verify header version.", + mapped)); + } +} + + +bool +Test::testReadSizeError(DataBuffer &buf, const std::string &expected, + bool mapped) +{ + uint32_t headerLen = 0u; + try { + if (mapped) { + GenericHeader::MMapReader reader(buf.getData(), buf.getDataLen()); + headerLen = FileHeader::readSize(reader); + } else { + GenericHeader::BufferReader reader(buf); + headerLen = FileHeader::readSize(reader); + } + EXPECT_TRUE(false); + return false; + } catch (IllegalHeaderException &e) { + if (!EXPECT_EQUAL(expected, e.getMessage())) { + return false; + } + } + EXPECT_EQUAL(headerLen, 0u); + return true; +} + diff --git a/vespalib/src/tests/metrics/CMakeLists.txt b/vespalib/src/tests/metrics/CMakeLists.txt new file mode 100644 index 00000000000..6019a6c2d4c --- /dev/null +++ b/vespalib/src/tests/metrics/CMakeLists.txt @@ -0,0 +1,17 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vespalib_metrics_test_app TEST + SOURCES + simple_metrics_test.cpp + mock_tick.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_metrics_test_app COMMAND vespalib_metrics_test_app) + +vespa_add_executable(vespalib_stablestore_test_app TEST + SOURCES + stable_store_test.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_stablestore_test_app COMMAND vespalib_stablestore_test_app) diff --git a/vespalib/src/tests/metrics/mock_tick.cpp b/vespalib/src/tests/metrics/mock_tick.cpp new file mode 100644 index 00000000000..c16ef25cfe6 --- /dev/null +++ b/vespalib/src/tests/metrics/mock_tick.cpp @@ -0,0 +1,6 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include "mock_tick.h" + +namespace vespalib::metrics { + +} // namespace vespalib::metrics diff --git a/vespalib/src/tests/metrics/mock_tick.h b/vespalib/src/tests/metrics/mock_tick.h new file mode 100644 index 00000000000..4d9f6758537 --- /dev/null +++ b/vespalib/src/tests/metrics/mock_tick.h @@ -0,0 +1,92 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include <atomic> +#include <condition_variable> +#include <mutex> +#include <vespa/vespalib/metrics/clock.h> +#include <vespa/vespalib/testkit/test_kit.h> + +namespace vespalib::metrics { + +// used to test clients of the Tick interface +// values shared between threads are bounded queues with max size 1 +class MockTick : public Tick { +private: + using Guard = std::unique_lock<std::mutex>; + struct Value { + Value() noexcept : value(0.0), valid(false) {} + TimeStamp value; + bool valid; + }; + + TimeStamp _first_value; + std::mutex _lock; + std::condition_variable _cond; + bool _alive; + Value _prev; + Value _next; + + void push(Value &dst, TimeStamp value) { + Guard guard(_lock); + while (_alive && dst.valid) { + _cond.wait(guard); + } + dst.value = value; + dst.valid = true; + _cond.notify_one(); + } + + TimeStamp pop(Value &src) { + Guard guard(_lock); + while (_alive && !src.valid) { + _cond.wait(guard); + } + src.valid = false; + _cond.notify_one(); + return src.value; + } + + TimeStamp peek(const Value &src) { + Guard guard(_lock); + while (_alive && !src.valid) { + _cond.wait(guard); + } + return src.value; + } + +public: + explicit MockTick(TimeStamp first_value) noexcept + : _first_value(first_value), _lock(), _cond(), _alive(true), _prev(), _next() {} + TimeStamp first() override { return _first_value; } + TimeStamp next(TimeStamp prev) override { + push(_prev, prev); + return pop(_next); + } + TimeStamp give(TimeStamp next_value) { + TimeStamp prev_value = pop(_prev); + push(_next, next_value); + EXPECT_EQUAL(peek(_prev).count(), next_value.count()); + return prev_value; + } + bool alive() const override { return _alive; } + void kill() override { + Guard guard(_lock); + _alive = false; + _cond.notify_all(); + } +}; + +// share the MockTick between the tested and the tester. +class TickProxy : public Tick { +private: + std::shared_ptr<Tick> _tick; +public: + explicit TickProxy(std::shared_ptr<Tick> tick) noexcept : _tick(std::move(tick)) {} + TimeStamp first() override { return _tick->first(); } + TimeStamp next(TimeStamp prev) override { return _tick->next(prev); } + bool alive() const override { return _tick->alive(); } + void kill() override { _tick->kill(); } +}; + +} // namespace vespalib::metrics diff --git a/vespalib/src/tests/metrics/simple_metrics_test.cpp b/vespalib/src/tests/metrics/simple_metrics_test.cpp new file mode 100644 index 00000000000..3006022a43d --- /dev/null +++ b/vespalib/src/tests/metrics/simple_metrics_test.cpp @@ -0,0 +1,219 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/data/slime/slime.h> +#include <vespa/vespalib/data/slime/json_format.h> +#include <vespa/vespalib/metrics/simple_metrics.h> +#include <vespa/vespalib/metrics/simple_metrics_manager.h> +#include <vespa/vespalib/metrics/stable_store.h> +#include <vespa/vespalib/metrics/json_formatter.h> +#include "mock_tick.h" +#include <stdio.h> +#include <unistd.h> + +using namespace vespalib; +using namespace vespalib::metrics; + +TEST("require that simple metrics gauge merge works") +{ + std::pair<MetricId, Point> id(MetricId(42), Point(17)); + Gauge::Measurement a1(id, 0.0); + Gauge::Measurement b1(id, 7.0); + Gauge::Measurement b2(id, 9.0); + Gauge::Measurement b3(id, 8.0); + Gauge::Measurement c1(id, 10.0); + Gauge::Measurement c2(id, 1.0); + + GaugeAggregator a(a1), b(b1), c(c1); + b.merge(b2); + b.merge(b3); + c.merge(c2); + + EXPECT_EQUAL(a.observedCount, 1u); + EXPECT_EQUAL(a.sumValue, 0.0); + EXPECT_EQUAL(a.minValue, 0.0); + EXPECT_EQUAL(a.maxValue, 0.0); + EXPECT_EQUAL(a.lastValue, 0.0); + + EXPECT_EQUAL(b.observedCount, 3u); + EXPECT_EQUAL(b.sumValue, 24.0); + EXPECT_EQUAL(b.minValue, 7.0); + EXPECT_EQUAL(b.maxValue, 9.0); + EXPECT_EQUAL(b.lastValue, 8.0); + + EXPECT_EQUAL(c.observedCount, 2u); + EXPECT_EQUAL(c.sumValue, 11.0); + EXPECT_EQUAL(c.minValue, 1.0); + EXPECT_EQUAL(c.maxValue, 10.0); + EXPECT_EQUAL(c.lastValue, 1.0); + + a.minValue = 8; + + a.merge(b); + EXPECT_EQUAL(a.observedCount, 4u); + EXPECT_EQUAL(a.sumValue, 24.0); + EXPECT_EQUAL(a.minValue, 7.0); + EXPECT_EQUAL(a.maxValue, 9.0); + EXPECT_EQUAL(a.lastValue, 8.0); + + a.merge(b); + EXPECT_EQUAL(a.observedCount, 7u); + EXPECT_EQUAL(a.sumValue, 48.0); + EXPECT_EQUAL(a.minValue, 7.0); + EXPECT_EQUAL(a.maxValue, 9.0); + EXPECT_EQUAL(a.lastValue, 8.0); + + a.merge(c); + EXPECT_EQUAL(a.observedCount, 9u); + EXPECT_EQUAL(a.sumValue, 59.0); + EXPECT_EQUAL(a.minValue, 1.0); + EXPECT_EQUAL(a.maxValue, 10.0); + EXPECT_EQUAL(a.lastValue, 1.0); +} + +bool compare_json(const vespalib::string &a, const vespalib::string &b) +{ + using vespalib::Memory; + using vespalib::slime::JsonFormat; + + Slime slimeA, slimeB; + if (! JsonFormat::decode(a, slimeA)) { +fprintf(stderr, "bad json a:\n>>>%s\n<<<\n", a.c_str()); + return false; + } + if (! JsonFormat::decode(b, slimeB)) { +fprintf(stderr, "bad json b\n"); + return false; + } + if (!(slimeA == slimeB)) { +fprintf(stderr, "compares unequal:\n[A]\n%s\n[B]\n%s\n", a.c_str(), b.c_str()); + } + return slimeA == slimeB; +} + +void check_json(const vespalib::string &actual) +{ + vespalib::string expect = "{" + " snapshot: { from: 1, to: 4 }," + " values: [ { name: 'foo'," + " values: { count: 17, rate: 4.85714 }" + " }, {" + " name: 'foo'," + " dimensions: { chain: 'default', documenttype: 'music', thread: '0' }," + " values: { count: 4, rate: 1.14286 }" + " }, {" + " name: 'bar'," + " values: { count: 4, rate: 1.14286, average: 42, sum: 168, min: 41, max: 43, last: 42 }" + " }, {" + " name: 'bar'," + " dimensions: { chain: 'vespa', documenttype: 'blogpost', thread: '1' }," + " values: { count: 1, rate: 0.285714, average: 14, sum: 14, min: 14, max: 14, last: 14 }" + " }, {" + " name: 'bar'," + " dimensions: { chain: 'vespa', documenttype: 'blogpost', thread: '2' }," + " values: { count: 1, rate: 0.285714, average: 11, sum: 11, min: 11, max: 11, last: 11 }" + " } ]" + "}"; + EXPECT_TRUE(compare_json(expect, actual)); +} + + +TEST("use simple_metrics_collector") +{ + using namespace vespalib::metrics; + SimpleManagerConfig cf; + cf.sliding_window_seconds = 5; + std::shared_ptr<MockTick> ticker = std::make_shared<MockTick>(TimeStamp(1.0)); + auto manager = SimpleMetricsManager::createForTest(cf, std::make_unique<TickProxy>(ticker)); + + Counter myCounter = manager->counter("foo", "no description"); + myCounter.add(); + myCounter.add(16); + + Gauge myGauge = manager->gauge("bar", "dummy description"); + myGauge.sample(42.0); + myGauge.sample(41.0); + myGauge.sample(43.0); + myGauge.sample(42.0); + + EXPECT_EQUAL(1.0, ticker->give(TimeStamp(2.0)).count()); + + Snapshot snap1 = manager->snapshot(); + EXPECT_EQUAL(1.0, snap1.startTime()); + EXPECT_EQUAL(2.0, snap1.endTime()); + + EXPECT_EQUAL(1u, snap1.counters().size()); + EXPECT_EQUAL("foo", snap1.counters()[0].name()); + EXPECT_EQUAL(17u, snap1.counters()[0].count()); + + EXPECT_EQUAL(1u, snap1.gauges().size()); + EXPECT_EQUAL("bar", snap1.gauges()[0].name()); + EXPECT_EQUAL(4u, snap1.gauges()[0].observedCount()); + EXPECT_EQUAL(41.0, snap1.gauges()[0].minValue()); + EXPECT_EQUAL(43.0, snap1.gauges()[0].maxValue()); + EXPECT_EQUAL(42.0, snap1.gauges()[0].lastValue()); + + Point one = manager->pointBuilder() + .bind("chain", "default") + .bind("documenttype", "music") + .bind("thread", "0").build(); + PointBuilder b2 = manager->pointBuilder(); + b2.bind("chain", "vespa") + .bind("documenttype", "blogpost"); + b2.bind("thread", "1"); + Point two = b2.build(); + EXPECT_EQUAL(one.id(), 1u); + EXPECT_EQUAL(two.id(), 2u); + + Point anotherOne = manager->pointBuilder() + .bind("chain", "default") + .bind("documenttype", "music") + .bind("thread", "0"); + EXPECT_EQUAL(anotherOne.id(), 1u); + + Point three = manager->pointBuilder(two).bind("thread", "2"); + EXPECT_EQUAL(three.id(), 3u); + + myCounter.add(3, one); + myCounter.add(one); + myGauge.sample(14.0, two); + myGauge.sample(11.0, three); + + EXPECT_EQUAL(2.0, ticker->give(TimeStamp(4.5)).count()); + + Snapshot snap2 = manager->snapshot(); + EXPECT_EQUAL(1.0, snap2.startTime()); + EXPECT_EQUAL(4.5, snap2.endTime()); + EXPECT_EQUAL(2u, snap2.counters().size()); + EXPECT_EQUAL(3u, snap2.gauges().size()); + + JsonFormatter fmt2(snap2); + check_json(fmt2.asString()); + + // flush sliding window + for (int i = 5; i <= 10; ++i) { + ticker->give(TimeStamp(i)); + } + Snapshot snap3 = manager->snapshot(); + EXPECT_EQUAL(5.0, snap3.startTime()); + EXPECT_EQUAL(10.0, snap3.endTime()); + EXPECT_EQUAL(2u, snap3.counters().size()); + EXPECT_EQUAL(0u, snap3.counters()[0].count()); + EXPECT_EQUAL(0u, snap3.counters()[1].count()); + EXPECT_EQUAL(3u, snap3.gauges().size()); + EXPECT_EQUAL(0u, snap3.gauges()[0].observedCount()); + EXPECT_EQUAL(0u, snap3.gauges()[1].observedCount()); + EXPECT_EQUAL(0u, snap3.gauges()[2].observedCount()); + + Snapshot snap4 = manager->totalSnapshot(); + EXPECT_EQUAL(1.0, snap4.startTime()); + EXPECT_EQUAL(10.0, snap4.endTime()); + EXPECT_EQUAL(2u, snap4.counters().size()); + EXPECT_NOT_EQUAL(0u, snap4.counters()[0].count()); + EXPECT_NOT_EQUAL(0u, snap4.counters()[1].count()); + EXPECT_EQUAL(3u, snap4.gauges().size()); + EXPECT_NOT_EQUAL(0u, snap4.gauges()[0].observedCount()); + EXPECT_NOT_EQUAL(0u, snap4.gauges()[1].observedCount()); + EXPECT_NOT_EQUAL(0u, snap4.gauges()[2].observedCount()); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/tests/metrics/stable_store_test.cpp b/vespalib/src/tests/metrics/stable_store_test.cpp new file mode 100644 index 00000000000..cead112069f --- /dev/null +++ b/vespalib/src/tests/metrics/stable_store_test.cpp @@ -0,0 +1,65 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/metrics/simple_metrics.h> +#include <vespa/vespalib/metrics/simple_metrics_manager.h> +#include <vespa/vespalib/metrics/stable_store.h> +#include <vespa/vespalib/metrics/json_formatter.h> +#include <stdio.h> +#include <unistd.h> + +using namespace vespalib; +using namespace vespalib::metrics; + +struct Foo { + int a; + char *p; + explicit Foo(int v) : a(v), p(nullptr) {} + bool operator==(const Foo &other) const { + return a == other.a; + } +}; + +TEST("require that stable_store works") +{ + vespalib::StableStore<Foo> bunch; + bunch.add(Foo(1)); + bunch.add(Foo(2)); + bunch.add(Foo(3)); + bunch.add(Foo(5)); + bunch.add(Foo(8)); + bunch.add(Foo(13)); + bunch.add(Foo(21)); + bunch.add(Foo(34)); + bunch.add(Foo(55)); + bunch.add(Foo(89)); + + EXPECT_EQUAL(bunch.size(), 10u); + + int sum = 0; + + bunch.for_each([&sum](const Foo& value) { sum += value.a; }); + EXPECT_EQUAL(231, sum); + + std::vector<const Foo *> pointers; + bunch.for_each([&pointers](const Foo& value) + { pointers.push_back(&value); }); + EXPECT_EQUAL(1, pointers[0]->a); + EXPECT_EQUAL(2, pointers[1]->a); + EXPECT_EQUAL(55, pointers[8]->a); + EXPECT_EQUAL(89, pointers[9]->a); + + for (int i = 0; i < 20000; ++i) { + bunch.add(Foo(i)); + } + bunch.for_each([&sum](const Foo& value) { sum -= value.a; }); + EXPECT_EQUAL(-199990000, sum); + + std::vector<const Foo *> after; + bunch.for_each([&after](const Foo& value) + { if (after.size() < 10) after.push_back(&value); }); + + EXPECT_EQUAL(pointers[0], after[0]); + EXPECT_EQUAL(pointers[9], after[9]); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/tests/sequencedtaskexecutor/.gitignore b/vespalib/src/tests/sequencedtaskexecutor/.gitignore new file mode 100644 index 00000000000..3b6f7c74a67 --- /dev/null +++ b/vespalib/src/tests/sequencedtaskexecutor/.gitignore @@ -0,0 +1,4 @@ +vespalib_sequencedtaskexecutor_test_app +vespalib_sequencedtaskexecutor_benchmark_app +vespalib_adaptive_sequenced_executor_test_app +vespalib_foregroundtaskexecutor_test_app diff --git a/vespalib/src/tests/sequencedtaskexecutor/CMakeLists.txt b/vespalib/src/tests/sequencedtaskexecutor/CMakeLists.txt new file mode 100644 index 00000000000..6a488b3c716 --- /dev/null +++ b/vespalib/src/tests/sequencedtaskexecutor/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vespalib_sequencedtaskexecutor_benchmark_app TEST + SOURCES + sequencedtaskexecutor_benchmark.cpp + DEPENDS + vespalib +) + +vespa_add_executable(vespalib_sequencedtaskexecutor_test_app TEST + SOURCES + sequencedtaskexecutor_test.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_sequencedtaskexecutor_test_app COMMAND vespalib_sequencedtaskexecutor_test_app) + +vespa_add_executable(vespalib_adaptive_sequenced_executor_test_app TEST + SOURCES + adaptive_sequenced_executor_test.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_adaptive_sequenced_executor_test_app COMMAND vespalib_adaptive_sequenced_executor_test_app) + +vespa_add_executable(vespalib_foregroundtaskexecutor_test_app TEST + SOURCES + foregroundtaskexecutor_test.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_foregroundtaskexecutor_test_app COMMAND vespalib_foregroundtaskexecutor_test_app) diff --git a/vespalib/src/tests/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp b/vespalib/src/tests/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp new file mode 100644 index 00000000000..1a458f86232 --- /dev/null +++ b/vespalib/src/tests/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp @@ -0,0 +1,248 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/util/adaptive_sequenced_executor.h> +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/test/insertion_operators.h> + +#include <condition_variable> +#include <unistd.h> + +#include <vespa/log/log.h> +LOG_SETUP("adaptive_sequenced_executor_test"); + +namespace vespalib { + + +class Fixture +{ +public: + AdaptiveSequencedExecutor _threads; + + Fixture(bool is_max_pending_hard=true) : _threads(2, 2, 0, 1000, is_max_pending_hard) { } +}; + + +class TestObj +{ +public: + std::mutex _m; + std::condition_variable _cv; + int _done; + int _fail; + int _val; + + TestObj() noexcept + : _m(), + _cv(), + _done(0), + _fail(0), + _val(0) + { + } + + void + modify(int oldValue, int newValue) + { + { + std::lock_guard<std::mutex> guard(_m); + if (_val == oldValue) { + _val = newValue; + } else { + ++_fail; + } + ++_done; + _cv.notify_all(); + } + } + + void + wait(int wantDone) + { + std::unique_lock<std::mutex> guard(_m); + _cv.wait(guard, [&] { return this->_done >= wantDone; }); + } +}; + +vespalib::stringref ZERO("0"); + +TEST_F("testExecute", Fixture) { + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads.execute(1, [&]() { tv->modify(0, 42); }); + tv->wait(1); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); + f._threads.sync_all(); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); +} + + +TEST_F("require that task with same component id are serialized", Fixture) +{ + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads.execute(0, [&]() { usleep(2000); tv->modify(0, 14); }); + f._threads.execute(0, [&]() { tv->modify(14, 42); }); + tv->wait(2); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); + f._threads.sync_all(); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); +} + +TEST_F("require that task with different component ids are not serialized", Fixture) +{ + int tryCnt = 0; + for (tryCnt = 0; tryCnt < 100; ++tryCnt) { + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads.execute(0, [&]() { usleep(2000); tv->modify(0, 14); }); + f._threads.execute(1, [&]() { tv->modify(14, 42); }); + tv->wait(2); + if (tv->_fail != 1) { + continue; + } + EXPECT_EQUAL(1, tv->_fail); + EXPECT_EQUAL(14, tv->_val); + f._threads.sync_all(); + EXPECT_EQUAL(1, tv->_fail); + EXPECT_EQUAL(14, tv->_val); + break; + } + EXPECT_TRUE(tryCnt < 100); +} + + +TEST_F("require that task with same string component id are serialized", Fixture) +{ + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + auto test2 = [&]() { tv->modify(14, 42); }; + f._threads.execute(f._threads.getExecutorIdFromName(ZERO), [&]() { usleep(2000); tv->modify(0, 14); }); + f._threads.execute(f._threads.getExecutorIdFromName(ZERO), test2); + tv->wait(2); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); + f._threads.sync_all(); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); +} + +namespace { + +int detectSerializeFailure(Fixture &f, vespalib::stringref altComponentId, int tryLimit) +{ + int tryCnt = 0; + for (tryCnt = 0; tryCnt < tryLimit; ++tryCnt) { + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads.execute(f._threads.getExecutorIdFromName(ZERO), [&]() { usleep(2000); tv->modify(0, 14); }); + f._threads.execute(f._threads.getExecutorIdFromName(altComponentId), [&]() { tv->modify(14, 42); }); + tv->wait(2); + if (tv->_fail != 1) { + continue; + } + EXPECT_EQUAL(1, tv->_fail); + EXPECT_EQUAL(14, tv->_val); + f._threads.sync_all(); + EXPECT_EQUAL(1, tv->_fail); + EXPECT_EQUAL(14, tv->_val); + break; + } + return tryCnt; +} + +vespalib::string makeAltComponentId(Fixture &f) +{ + int tryCnt = 0; + char altComponentId[20]; + ISequencedTaskExecutor::ExecutorId executorId0 = f._threads.getExecutorIdFromName(ZERO); + for (tryCnt = 1; tryCnt < 100; ++tryCnt) { + sprintf(altComponentId, "%d", tryCnt); + if (f._threads.getExecutorIdFromName(altComponentId) == executorId0) { + break; + } + } + EXPECT_TRUE(tryCnt < 100); + return altComponentId; +} + +} + +TEST_F("require that task with different string component ids are not serialized", Fixture) +{ + int tryCnt = detectSerializeFailure(f, "2", 100); + EXPECT_TRUE(tryCnt < 100); +} + + +TEST_F("require that task with different string component ids mapping to the same executor id are serialized", + Fixture) +{ + vespalib::string altComponentId = makeAltComponentId(f); + LOG(info, "second string component id is \"%s\"", altComponentId.c_str()); + int tryCnt = detectSerializeFailure(f, altComponentId, 100); + EXPECT_TRUE(tryCnt == 100); +} + + +TEST_F("require that execute works with const lambda", Fixture) +{ + int i = 5; + std::vector<int> res; + const auto lambda = [i, &res]() mutable + { res.push_back(i--); res.push_back(i--); }; + f._threads.execute(0, lambda); + f._threads.execute(0, lambda); + f._threads.sync_all(); + std::vector<int> exp({5, 4, 5, 4}); + EXPECT_EQUAL(exp, res); + EXPECT_EQUAL(5, i); +} + +TEST_F("require that execute works with reference to lambda", Fixture) +{ + int i = 5; + std::vector<int> res; + auto lambda = [i, &res]() mutable + { res.push_back(i--); res.push_back(i--); }; + auto &lambdaref = lambda; + f._threads.execute(0, lambdaref); + f._threads.execute(0, lambdaref); + f._threads.sync_all(); + std::vector<int> exp({5, 4, 5, 4}); + EXPECT_EQUAL(exp, res); + EXPECT_EQUAL(5, i); +} + +TEST_F("require that executeLambda works", Fixture) +{ + int i = 5; + std::vector<int> res; + const auto lambda = [i, &res]() mutable + { res.push_back(i--); res.push_back(i--); }; + f._threads.executeLambda(ISequencedTaskExecutor::ExecutorId(0), lambda); + f._threads.sync_all(); + std::vector<int> exp({5, 4}); + EXPECT_EQUAL(exp, res); + EXPECT_EQUAL(5, i); +} + +TEST("require that you get correct number of executors") { + AdaptiveSequencedExecutor seven(7, 1, 0, 10, true); + EXPECT_EQUAL(7u, seven.getNumExecutors()); +} + +TEST("require that you distribute well") { + AdaptiveSequencedExecutor seven(7, 1, 0, 10, true); + EXPECT_EQUAL(7u, seven.getNumExecutors()); + for (uint32_t id=0; id < 1000; id++) { + EXPECT_EQUAL(id%7, seven.getExecutorId(id).getId()); + } +} + +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/tests/sequencedtaskexecutor/foregroundtaskexecutor_test.cpp b/vespalib/src/tests/sequencedtaskexecutor/foregroundtaskexecutor_test.cpp new file mode 100644 index 00000000000..56fb570209c --- /dev/null +++ b/vespalib/src/tests/sequencedtaskexecutor/foregroundtaskexecutor_test.cpp @@ -0,0 +1,120 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/util/foregroundtaskexecutor.h> +#include <vespa/vespalib/testkit/testapp.h> + +#include <condition_variable> +#include <unistd.h> + +#include <vespa/log/log.h> +LOG_SETUP("foregroundtaskexecutor_test"); + +namespace vespalib { + + +class Fixture +{ +public: + ForegroundTaskExecutor _threads; + + Fixture() + : _threads() + { + } +}; + + +class TestObj +{ +public: + std::mutex _m; + std::condition_variable _cv; + int _done; + int _fail; + int _val; + + TestObj() noexcept + : _m(), + _cv(), + _done(0), + _fail(0), + _val(0) + { + } + + void + modify(int oldValue, int newValue) + { + { + std::lock_guard<std::mutex> guard(_m); + if (_val == oldValue) { + _val = newValue; + } else { + ++_fail; + } + ++_done; + } + _cv.notify_all(); + } + + void + wait(int wantDone) + { + std::unique_lock<std::mutex> guard(_m); + _cv.wait(guard, [this, wantDone] { return this->_done >= wantDone; }); + } +}; + +TEST_F("testExecute", Fixture) { + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads.execute(1, [=]() { tv->modify(0, 42); }); + tv->wait(1); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); + f._threads.sync_all(); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); +} + + +TEST_F("require that task with same id are serialized", Fixture) +{ + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads.execute(0, [=]() { usleep(2000); tv->modify(0, 14); }); + f._threads.execute(0, [=]() { tv->modify(14, 42); }); + tv->wait(2); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); + f._threads.sync_all(); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); +} + +TEST_F("require that task with different ids are serialized", Fixture) +{ + int tryCnt = 0; + for (tryCnt = 0; tryCnt < 100; ++tryCnt) { + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads.execute(0, [=]() { usleep(2000); tv->modify(0, 14); }); + f._threads.execute(1, [=]() { tv->modify(14, 42); }); + tv->wait(2); + if (tv->_fail != 1) { + continue; + } + EXPECT_EQUAL(1, tv->_fail); + EXPECT_EQUAL(14, tv->_val); + f._threads.sync_all(); + EXPECT_EQUAL(1, tv->_fail); + EXPECT_EQUAL(14, tv->_val); + break; + } + EXPECT_TRUE(tryCnt >= 100); +} + + +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp b/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp new file mode 100644 index 00000000000..0f7c82ef988 --- /dev/null +++ b/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp @@ -0,0 +1,73 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/util/sequencedtaskexecutor.h> +#include <vespa/vespalib/util/adaptive_sequenced_executor.h> +#include <vespa/vespalib/util/lambdatask.h> +#include <vespa/vespalib/util/time.h> +#include <atomic> +#include <cinttypes> + +using vespalib::ISequencedTaskExecutor; +using vespalib::SequencedTaskExecutor; +using vespalib::AdaptiveSequencedExecutor; +using ExecutorId = vespalib::ISequencedTaskExecutor::ExecutorId; + +size_t do_work(size_t size) { + size_t ret = 0; + for (size_t i = 0; i < size; ++i) { + for (size_t j = 0; j < 128; ++j) { + ret = (ret + i) * j; + } + } + return ret; +} + +struct SimpleParams { + int argc; + char **argv; + int idx; + SimpleParams(int argc_in, char **argv_in) : argc(argc_in), argv(argv_in), idx(0) {} + int next(const char *name, int fallback) { + ++idx; + int value = 0; + if (argc > idx) { + value = atoi(argv[idx]); + } else { + value = fallback; + } + fprintf(stderr, "param %s: %d\n", name, value); + return value; + } +}; + +VESPA_THREAD_STACK_TAG(sequenced_executor) + +int main(int argc, char **argv) { + SimpleParams params(argc, argv); + bool use_adaptive_executor = params.next("use_adaptive_executor", 0); + bool optimize_for_throughput = params.next("optimize_for_throughput", 0); + size_t num_tasks = params.next("num_tasks", 1000000); + size_t num_strands = params.next("num_strands", 4); + size_t task_limit = params.next("task_limit", 1000); + size_t num_threads = params.next("num_threads", num_strands); + size_t max_waiting = params.next("max_waiting", optimize_for_throughput ? 32 : 0); + size_t work_size = params.next("work_size", 0); + std::atomic<long> counter(0); + std::unique_ptr<ISequencedTaskExecutor> executor; + if (use_adaptive_executor) { + executor = std::make_unique<AdaptiveSequencedExecutor>(num_strands, num_threads, max_waiting, task_limit, true); + } else { + auto optimize = optimize_for_throughput + ? vespalib::Executor::OptimizeFor::THROUGHPUT + : vespalib::Executor::OptimizeFor::LATENCY; + executor = SequencedTaskExecutor::create(sequenced_executor, num_strands, task_limit, true, optimize); + } + vespalib::Timer timer; + for (size_t task_id = 0; task_id < num_tasks; ++task_id) { + executor->executeTask(ExecutorId(task_id % num_strands), + vespalib::makeLambdaTask([&counter,work_size] { (void) do_work(work_size); counter++; })); + } + executor.reset(); + fprintf(stderr, "\ntotal time: %" PRId64 " ms\n", vespalib::count_ms(timer.elapsed())); + return (size_t(counter) == num_tasks) ? 0 : 1; +} diff --git a/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_test.cpp b/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_test.cpp new file mode 100644 index 00000000000..705d6346e8c --- /dev/null +++ b/vespalib/src/tests/sequencedtaskexecutor/sequencedtaskexecutor_test.cpp @@ -0,0 +1,351 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/util/sequencedtaskexecutor.h> +#include <vespa/vespalib/util/adaptive_sequenced_executor.h> +#include <vespa/vespalib/util/blockingthreadstackexecutor.h> +#include <vespa/vespalib/util/singleexecutor.h> +#include <vespa/vespalib/testkit/testapp.h> +#include <vespa/vespalib/test/insertion_operators.h> + +#include <condition_variable> +#include <unistd.h> + +#include <vespa/log/log.h> +LOG_SETUP("sequencedtaskexecutor_test"); + +VESPA_THREAD_STACK_TAG(sequenced_executor) + +namespace vespalib { + + +class Fixture +{ +public: + std::unique_ptr<ISequencedTaskExecutor> _threads; + + Fixture(bool is_task_limit_hard = true) : + _threads(SequencedTaskExecutor::create(sequenced_executor, 2, 1000, is_task_limit_hard, + Executor::OptimizeFor::LATENCY)) + { } +}; + + +class TestObj +{ +public: + std::mutex _m; + std::condition_variable _cv; + int _done; + int _fail; + int _val; + + TestObj() noexcept + : _m(), + _cv(), + _done(0), + _fail(0), + _val(0) + { + } + + void + modify(int oldValue, int newValue) + { + { + std::lock_guard<std::mutex> guard(_m); + if (_val == oldValue) { + _val = newValue; + } else { + ++_fail; + } + ++_done; + } + _cv.notify_all(); + } + + void + wait(int wantDone) + { + std::unique_lock<std::mutex> guard(_m); + _cv.wait(guard, [this, wantDone] { return this->_done >= wantDone; }); + } +}; + +vespalib::stringref ZERO("0"); + +TEST_F("testExecute", Fixture) { + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads->execute(1, [=]() { tv->modify(0, 42); }); + tv->wait(1); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); + f._threads->sync_all(); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); +} + + +TEST_F("require that task with same component id are serialized", Fixture) +{ + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads->execute(0, [=]() { usleep(2000); tv->modify(0, 14); }); + f._threads->execute(0, [=]() { tv->modify(14, 42); }); + tv->wait(2); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); + f._threads->sync_all(); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); +} + +TEST_F("require that task with same component id are serialized when executed with list", Fixture) +{ + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + ISequencedTaskExecutor::ExecutorId executorId = f._threads->getExecutorId(0); + ISequencedTaskExecutor::TaskList list; + list.template emplace_back(executorId, makeLambdaTask([=]() { usleep(2000); tv->modify(0, 14); })); + list.template emplace_back(executorId, makeLambdaTask([=]() { tv->modify(14, 42); })); + f._threads->executeTasks(std::move(list)); + tv->wait(2); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); + f._threads->sync_all(); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); +} + +TEST_F("require that task with different component ids are not serialized", Fixture) +{ + int tryCnt = 0; + for (tryCnt = 0; tryCnt < 100; ++tryCnt) { + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads->execute(0, [=]() { usleep(2000); tv->modify(0, 14); }); + f._threads->execute(2, [=]() { tv->modify(14, 42); }); + tv->wait(2); + if (tv->_fail != 1) { + continue; + } + EXPECT_EQUAL(1, tv->_fail); + EXPECT_EQUAL(14, tv->_val); + f._threads->sync_all(); + EXPECT_EQUAL(1, tv->_fail); + EXPECT_EQUAL(14, tv->_val); + break; + } + EXPECT_TRUE(tryCnt < 100); +} + + +TEST_F("require that task with same string component id are serialized", Fixture) +{ + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + auto test2 = [=]() { tv->modify(14, 42); }; + f._threads->execute(f._threads->getExecutorIdFromName(ZERO), [=]() { usleep(2000); tv->modify(0, 14); }); + f._threads->execute(f._threads->getExecutorIdFromName(ZERO), test2); + tv->wait(2); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); + f._threads->sync_all(); + EXPECT_EQUAL(0, tv->_fail); + EXPECT_EQUAL(42, tv->_val); +} + +namespace { + +int +detectSerializeFailure(Fixture &f, vespalib::stringref altComponentId, int tryLimit) +{ + int tryCnt = 0; + for (tryCnt = 0; tryCnt < tryLimit; ++tryCnt) { + std::shared_ptr<TestObj> tv(std::make_shared<TestObj>()); + EXPECT_EQUAL(0, tv->_val); + f._threads->execute(f._threads->getExecutorIdFromName(ZERO), [=]() { usleep(2000); tv->modify(0, 14); }); + f._threads->execute(f._threads->getExecutorIdFromName(altComponentId), [=]() { tv->modify(14, 42); }); + tv->wait(2); + if (tv->_fail != 1) { + continue; + } + EXPECT_EQUAL(1, tv->_fail); + EXPECT_EQUAL(14, tv->_val); + f._threads->sync_all(); + EXPECT_EQUAL(1, tv->_fail); + EXPECT_EQUAL(14, tv->_val); + break; + } + return tryCnt; +} + +vespalib::string +makeAltComponentId(Fixture &f) +{ + int tryCnt = 0; + char altComponentId[20]; + ISequencedTaskExecutor::ExecutorId executorId0 = f._threads->getExecutorIdFromName(ZERO); + for (tryCnt = 1; tryCnt < 100; ++tryCnt) { + sprintf(altComponentId, "%d", tryCnt); + if (f._threads->getExecutorIdFromName(altComponentId) == executorId0) { + break; + } + } + EXPECT_TRUE(tryCnt < 100); + return altComponentId; +} + +} + +TEST_F("require that task with different string component ids are not serialized", Fixture) +{ + int tryCnt = detectSerializeFailure(f, "2", 100); + EXPECT_TRUE(tryCnt < 100); +} + + +TEST_F("require that task with different string component ids mapping to the same executor id are serialized", + Fixture) +{ + vespalib::string altComponentId = makeAltComponentId(f); + LOG(info, "second string component id is \"%s\"", altComponentId.c_str()); + int tryCnt = detectSerializeFailure(f, altComponentId, 100); + EXPECT_TRUE(tryCnt == 100); +} + + +TEST_F("require that execute works with const lambda", Fixture) +{ + int i = 5; + std::vector<int> res; + const auto lambda = [i, &res]() mutable + { res.push_back(i--); res.push_back(i--); }; + f._threads->execute(0, lambda); + f._threads->execute(0, lambda); + f._threads->sync_all(); + std::vector<int> exp({5, 4, 5, 4}); + EXPECT_EQUAL(exp, res); + EXPECT_EQUAL(5, i); +} + +TEST_F("require that execute works with reference to lambda", Fixture) +{ + int i = 5; + std::vector<int> res; + auto lambda = [i, &res]() mutable + { res.push_back(i--); res.push_back(i--); }; + auto &lambdaref = lambda; + f._threads->execute(0, lambdaref); + f._threads->execute(0, lambdaref); + f._threads->sync_all(); + std::vector<int> exp({5, 4, 5, 4}); + EXPECT_EQUAL(exp, res); + EXPECT_EQUAL(5, i); +} + +TEST_F("require that executeLambda works", Fixture) +{ + int i = 5; + std::vector<int> res; + const auto lambda = [i, &res]() mutable + { res.push_back(i--); res.push_back(i--); }; + f._threads->executeLambda(ISequencedTaskExecutor::ExecutorId(0), lambda); + f._threads->sync_all(); + std::vector<int> exp({5, 4}); + EXPECT_EQUAL(exp, res); + EXPECT_EQUAL(5, i); +} + +TEST("require that you get correct number of executors") { + auto seven = SequencedTaskExecutor::create(sequenced_executor, 7); + EXPECT_EQUAL(7u, seven->getNumExecutors()); +} + +void verifyHardLimitForLatency(bool expect_hard) { + auto sequenced = SequencedTaskExecutor::create(sequenced_executor, 1, 100, expect_hard, Executor::OptimizeFor::LATENCY); + const SequencedTaskExecutor & seq = dynamic_cast<const SequencedTaskExecutor &>(*sequenced); + EXPECT_EQUAL(expect_hard,nullptr != dynamic_cast<const BlockingThreadStackExecutor *>(seq.first_executor())); +} + +void verifyHardLimitForThroughput(bool expect_hard) { + auto sequenced = SequencedTaskExecutor::create(sequenced_executor, 1, 100, expect_hard, Executor::OptimizeFor::THROUGHPUT); + const SequencedTaskExecutor & seq = dynamic_cast<const SequencedTaskExecutor &>(*sequenced); + const SingleExecutor * first = dynamic_cast<const SingleExecutor *>(seq.first_executor()); + EXPECT_TRUE(first != nullptr); + EXPECT_EQUAL(expect_hard, first->isBlocking()); +} + +TEST("require that you can get executor with both hard and soft limit") { + verifyHardLimitForLatency(true); + verifyHardLimitForLatency(false); + verifyHardLimitForThroughput(true); + verifyHardLimitForThroughput(false); +} + + +TEST("require that you distribute well") { + auto seven = SequencedTaskExecutor::create(sequenced_executor, 7); + const SequencedTaskExecutor & seq = dynamic_cast<const SequencedTaskExecutor &>(*seven); + const uint32_t NUM_EXACT = 8 * seven->getNumExecutors(); + EXPECT_EQUAL(7u, seven->getNumExecutors()); + EXPECT_EQUAL(97u, seq.getComponentHashSize()); + EXPECT_EQUAL(0u, seq.getComponentEffectiveHashSize()); + for (uint32_t id=0; id < 1000; id++) { + if (id < NUM_EXACT) { + EXPECT_EQUAL(id % seven->getNumExecutors(), seven->getExecutorId(id).getId()); + } else { + EXPECT_EQUAL(((id - NUM_EXACT) % 97) % seven->getNumExecutors(), seven->getExecutorId(id).getId()); + } + } + EXPECT_EQUAL(97u, seq.getComponentHashSize()); + EXPECT_EQUAL(97u, seq.getComponentEffectiveHashSize()); +} + +TEST("require that similar names get perfect distribution with 4 executors") { + auto four = SequencedTaskExecutor::create(sequenced_executor, 4); + EXPECT_EQUAL(0u, four->getExecutorIdFromName("f1").getId()); + EXPECT_EQUAL(1u, four->getExecutorIdFromName("f2").getId()); + EXPECT_EQUAL(2u, four->getExecutorIdFromName("f3").getId()); + EXPECT_EQUAL(3u, four->getExecutorIdFromName("f4").getId()); + EXPECT_EQUAL(0u, four->getExecutorIdFromName("f5").getId()); + EXPECT_EQUAL(1u, four->getExecutorIdFromName("f6").getId()); + EXPECT_EQUAL(2u, four->getExecutorIdFromName("f7").getId()); + EXPECT_EQUAL(3u, four->getExecutorIdFromName("f8").getId()); +} + +TEST("require that similar names get perfect distribution with 8 executors") { + auto four = SequencedTaskExecutor::create(sequenced_executor, 8); + EXPECT_EQUAL(0u, four->getExecutorIdFromName("f1").getId()); + EXPECT_EQUAL(1u, four->getExecutorIdFromName("f2").getId()); + EXPECT_EQUAL(2u, four->getExecutorIdFromName("f3").getId()); + EXPECT_EQUAL(3u, four->getExecutorIdFromName("f4").getId()); + EXPECT_EQUAL(4u, four->getExecutorIdFromName("f5").getId()); + EXPECT_EQUAL(5u, four->getExecutorIdFromName("f6").getId()); + EXPECT_EQUAL(6u, four->getExecutorIdFromName("f7").getId()); + EXPECT_EQUAL(7u, four->getExecutorIdFromName("f8").getId()); +} + +TEST("Test creation of different types") { + auto iseq = SequencedTaskExecutor::create(sequenced_executor, 1); + + EXPECT_EQUAL(1u, iseq->getNumExecutors()); + auto * seq = dynamic_cast<SequencedTaskExecutor *>(iseq.get()); + ASSERT_TRUE(seq != nullptr); + + iseq = SequencedTaskExecutor::create(sequenced_executor, 1, 1000, true, Executor::OptimizeFor::LATENCY); + seq = dynamic_cast<SequencedTaskExecutor *>(iseq.get()); + ASSERT_TRUE(seq != nullptr); + + iseq = SequencedTaskExecutor::create(sequenced_executor, 1, 1000, true, Executor::OptimizeFor::THROUGHPUT); + seq = dynamic_cast<SequencedTaskExecutor *>(iseq.get()); + ASSERT_TRUE(seq != nullptr); + + iseq = SequencedTaskExecutor::create(sequenced_executor, 1, 1000, true, Executor::OptimizeFor::ADAPTIVE, 17); + auto aseq = dynamic_cast<AdaptiveSequencedExecutor *>(iseq.get()); + ASSERT_TRUE(aseq != nullptr); +} + +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/tests/singleexecutor/CMakeLists.txt b/vespalib/src/tests/singleexecutor/CMakeLists.txt new file mode 100644 index 00000000000..3580a91d114 --- /dev/null +++ b/vespalib/src/tests/singleexecutor/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vespalib_singleexecutor_test_app TEST + SOURCES + singleexecutor_test.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_singleexecutor_test_app COMMAND vespalib_singleexecutor_test_app) diff --git a/vespalib/src/tests/singleexecutor/singleexecutor_test.cpp b/vespalib/src/tests/singleexecutor/singleexecutor_test.cpp new file mode 100644 index 00000000000..56352ff3c0d --- /dev/null +++ b/vespalib/src/tests/singleexecutor/singleexecutor_test.cpp @@ -0,0 +1,114 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/testkit/testapp.h> + +#include <vespa/vespalib/util/singleexecutor.h> +#include <vespa/vespalib/util/lambdatask.h> +#include <vespa/vespalib/util/alloc.h> +#include <atomic> + +using namespace vespalib; + +VESPA_THREAD_STACK_TAG(sequenced_executor) + +TEST("test that all tasks are executed") { + + std::atomic<uint64_t> counter(0); + SingleExecutor executor(sequenced_executor, 10); + + for (uint64_t i(0); i < 10; i++) { + executor.execute(makeLambdaTask([&counter] {counter++;})); + } + executor.sync(); + EXPECT_EQUAL(10u, counter); + + counter = 0; + for (uint64_t i(0); i < 10000; i++) { + executor.execute(makeLambdaTask([&counter] {counter++;})); + } + executor.sync(); + EXPECT_EQUAL(10000u, counter); +} + +TEST("test that executor can overflow") { + constexpr size_t NUM_TASKS = 1000; + std::atomic<uint64_t> counter(0); + vespalib::Gate gate; + SingleExecutor executor(sequenced_executor, 10, false, 1, 1ms); + executor.execute(makeLambdaTask([&gate] { gate.await();})); + + for(size_t i(0); i < NUM_TASKS; i++) { + executor.execute(makeLambdaTask([&counter, i] { + EXPECT_EQUAL(i, counter); + counter++; + })); + } + EXPECT_EQUAL(0u, counter); + ExecutorStats stats = executor.getStats(); + EXPECT_EQUAL(NUM_TASKS + 1, stats.acceptedTasks); + EXPECT_EQUAL(NUM_TASKS, stats.queueSize.max()); + gate.countDown(); + executor.sync(); + EXPECT_EQUAL(NUM_TASKS, counter); +} + +void verifyResizeTaskLimit(bool up) { + std::mutex lock; + std::condition_variable cond; + std::atomic<uint64_t> started(0); + std::atomic<uint64_t> allowed(0); + constexpr uint32_t INITIAL = 20; + const uint32_t INITIAL_2inN = roundUp2inN(INITIAL); + double waterMarkRatio = 0.5; + SingleExecutor executor(sequenced_executor, INITIAL, true, INITIAL*waterMarkRatio, 10ms); + EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit()); + EXPECT_EQUAL(uint32_t(INITIAL_2inN*waterMarkRatio), executor.get_watermark()); + + uint32_t targetTaskLimit = up ? 40 : 5; + uint32_t roundedTaskLimit = roundUp2inN(targetTaskLimit); + EXPECT_NOT_EQUAL(INITIAL_2inN, roundedTaskLimit); + + for (uint64_t i(0); i < INITIAL; i++) { + executor.execute(makeLambdaTask([&lock, &cond, &started, &allowed] { + started++; + std::unique_lock guard(lock); + while (allowed < started) { + cond.wait_for(guard, 1ms); + } + })); + } + while (started < 1); + EXPECT_EQUAL(1u, started); + executor.setTaskLimit(targetTaskLimit); + EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit()); + EXPECT_EQUAL(INITIAL_2inN*waterMarkRatio, executor.get_watermark()); + allowed = 5; + while (started < 6); + EXPECT_EQUAL(6u, started); + EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit()); + allowed = INITIAL; + while (started < INITIAL); + EXPECT_EQUAL(INITIAL, started); + EXPECT_EQUAL(INITIAL_2inN, executor.getTaskLimit()); + executor.execute(makeLambdaTask([&lock, &cond, &started, &allowed] { + started++; + std::unique_lock guard(lock); + while (allowed < started) { + cond.wait_for(guard, 1ms); + } + })); + while (started < INITIAL + 1); + EXPECT_EQUAL(INITIAL + 1, started); + EXPECT_EQUAL(roundedTaskLimit, executor.getTaskLimit()); + EXPECT_EQUAL(roundedTaskLimit*waterMarkRatio, executor.get_watermark()); + allowed = INITIAL + 1; +} + +TEST("test that resizing up and down works") { + TEST_DO(verifyResizeTaskLimit(true)); + TEST_DO(verifyResizeTaskLimit(false)); + + +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/vespalib/src/tests/state_server/.gitignore b/vespalib/src/tests/state_server/.gitignore new file mode 100644 index 00000000000..2d36d34e2ec --- /dev/null +++ b/vespalib/src/tests/state_server/.gitignore @@ -0,0 +1 @@ +vespalib_state_server_test_app diff --git a/vespalib/src/tests/state_server/CMakeLists.txt b/vespalib/src/tests/state_server/CMakeLists.txt new file mode 100644 index 00000000000..6d3d762a42b --- /dev/null +++ b/vespalib/src/tests/state_server/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vespalib_state_server_test_app TEST + SOURCES + state_server_test.cpp + DEPENDS + vespalib +) +vespa_add_test(NAME vespalib_state_server_test_app NO_VALGRIND NO_VALGRIND COMMAND vespalib_state_server_test_app ENVIRONMENT "VESPA_HOME=.") diff --git a/vespalib/src/tests/state_server/state_server_test.cpp b/vespalib/src/tests/state_server/state_server_test.cpp new file mode 100644 index 00000000000..f6e614f213a --- /dev/null +++ b/vespalib/src/tests/state_server/state_server_test.cpp @@ -0,0 +1,516 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/vespalib/testkit/test_kit.h> +#include <vespa/vespalib/net/http/state_server.h> +#include <vespa/vespalib/net/http/simple_health_producer.h> +#include <vespa/vespalib/net/http/simple_metrics_producer.h> +#include <vespa/vespalib/net/http/simple_component_config_producer.h> +#include <vespa/vespalib/net/http/state_explorer.h> +#include <vespa/vespalib/net/http/slime_explorer.h> +#include <vespa/vespalib/net/http/generic_state_handler.h> +#include <vespa/vespalib/util/stringfmt.h> +#include <vespa/vespalib/util/host_name.h> +#include <vespa/vespalib/process/process.h> +#include <sys/stat.h> + +using namespace vespalib; + +//----------------------------------------------------------------------------- + +vespalib::string root_path = "/state/v1/"; +vespalib::string short_root_path = "/state/v1"; +vespalib::string metrics_path = "/state/v1/metrics"; +vespalib::string health_path = "/state/v1/health"; +vespalib::string config_path = "/state/v1/config"; + +vespalib::string total_metrics_path = "/metrics/total"; + +vespalib::string unknown_path = "/this/path/is/not/known"; +vespalib::string unknown_state_path = "/state/v1/this/path/is/not/known"; +vespalib::string my_path = "/my/path"; + +vespalib::string host_tag = "HOST"; +std::map<vespalib::string,vespalib::string> empty_params; + +//----------------------------------------------------------------------------- + +vespalib::string run_cmd(const vespalib::string &cmd) { + vespalib::string out; + ASSERT_TRUE(Process::run(cmd.c_str(), out)); + return out; +} + +vespalib::string getPage(int port, const vespalib::string &path, const vespalib::string &extra_params = "") { + return run_cmd(make_string("curl -s %s 'http://localhost:%d%s'", extra_params.c_str(), port, path.c_str())); +} + +vespalib::string getFull(int port, const vespalib::string &path) { return getPage(port, path, "-D -"); } + +//----------------------------------------------------------------------------- + +struct DummyHandler : JsonGetHandler { + vespalib::string result; + DummyHandler(const vespalib::string &result_in) : result(result_in) {} + vespalib::string get(const vespalib::string &, const vespalib::string &, + const std::map<vespalib::string,vespalib::string> &) const override + { + return result; + } +}; + +//----------------------------------------------------------------------------- + +TEST_F("require that unknown url returns 404 response", HttpServer(0)) { + std::string expect("HTTP/1.1 404 Not Found\r\n" + "Connection: close\r\n" + "\r\n"); + std::string actual = getFull(f1.port(), unknown_path); + EXPECT_EQUAL(expect, actual); +} + +TEST_FF("require that empty known url returns 404 response", DummyHandler(""), HttpServer(0)) { + auto token = f2.repo().bind(my_path, f1); + std::string expect("HTTP/1.1 404 Not Found\r\n" + "Connection: close\r\n" + "\r\n"); + std::string actual = getFull(f2.port(), my_path); + EXPECT_EQUAL(expect, actual); +} + +TEST_FF("require that non-empty known url returns expected headers", DummyHandler("[123]"), HttpServer(0)) { + auto token = f2.repo().bind(my_path, f1); + vespalib::string expect("HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Content-Type: application/json\r\n" + "Content-Length: 5\r\n" + "X-XSS-Protection: 1; mode=block\r\n" + "X-Frame-Options: DENY\r\n" + "Content-Security-Policy: default-src 'none'\r\n" + "X-Content-Type-Options: nosniff\r\n" + "Cache-Control: no-store\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "[123]"); + std::string actual = getFull(f2.port(), my_path); + EXPECT_EQUAL(expect, actual); +} + +TEST_FFFF("require that handler is selected based on longest matching url prefix", + DummyHandler("[1]"), DummyHandler("[2]"), DummyHandler("[3]"), + HttpServer(0)) +{ + auto token2 = f4.repo().bind("/foo/bar", f2); + auto token1 = f4.repo().bind("/foo", f1); + auto token3 = f4.repo().bind("/foo/bar/baz", f3); + int port = f4.port(); + EXPECT_EQUAL("", getPage(port, "/fox")); + EXPECT_EQUAL("[1]", getPage(port, "/foo")); + EXPECT_EQUAL("[1]", getPage(port, "/foo/fox")); + EXPECT_EQUAL("[2]", getPage(port, "/foo/bar")); + EXPECT_EQUAL("[2]", getPage(port, "/foo/bar/fox")); + EXPECT_EQUAL("[3]", getPage(port, "/foo/bar/baz")); + EXPECT_EQUAL("[3]", getPage(port, "/foo/bar/baz/fox")); +} + +struct EchoHost : JsonGetHandler { + ~EchoHost() override; + vespalib::string get(const vespalib::string &host, const vespalib::string &, + const std::map<vespalib::string,vespalib::string> &) const override + { + return "[\"" + host + "\"]"; + } +}; + +EchoHost::~EchoHost() = default; + +TEST_FF("require that host is passed correctly", EchoHost(), HttpServer(0)) { + auto token = f2.repo().bind(my_path, f1); + EXPECT_EQUAL(make_string("%s:%d", HostName::get().c_str(), f2.port()), f2.host()); + vespalib::string default_result = make_string("[\"%s\"]", f2.host().c_str()); + vespalib::string localhost_result = make_string("[\"%s:%d\"]", "localhost", f2.port()); + vespalib::string silly_result = "[\"sillyserver\"]"; + EXPECT_EQUAL(localhost_result, run_cmd(make_string("curl -s http://localhost:%d/my/path", f2.port()))); + EXPECT_EQUAL(silly_result, run_cmd(make_string("curl -s http://localhost:%d/my/path -H \"Host: sillyserver\"", f2.port()))); + EXPECT_EQUAL(default_result, run_cmd(make_string("curl -s http://localhost:%d/my/path -H \"Host:\"", f2.port()))); +} + +struct SamplingHandler : JsonGetHandler { + mutable std::mutex my_lock; + mutable vespalib::string my_host; + mutable vespalib::string my_path; + mutable std::map<vespalib::string,vespalib::string> my_params; + ~SamplingHandler() override; + vespalib::string get(const vespalib::string &host, const vespalib::string &path, + const std::map<vespalib::string,vespalib::string> ¶ms) const override + { + { + auto guard = std::lock_guard(my_lock); + my_host = host; + my_path = path; + my_params = params; + } + return "[]"; + } +}; + +SamplingHandler::~SamplingHandler() = default; + +TEST_FF("require that request parameters can be inspected", SamplingHandler(), HttpServer(0)) +{ + auto token = f2.repo().bind("/foo", f1); + EXPECT_EQUAL("[]", getPage(f2.port(), "/foo?a=b&x=y&z")); + { + auto guard = std::lock_guard(f1.my_lock); + EXPECT_EQUAL(f1.my_path, "/foo"); + EXPECT_EQUAL(f1.my_params.size(), 3u); + EXPECT_EQUAL(f1.my_params["a"], "b"); + EXPECT_EQUAL(f1.my_params["x"], "y"); + EXPECT_EQUAL(f1.my_params["z"], ""); + EXPECT_EQUAL(f1.my_params.size(), 3u); // "z" was present + } +} + +TEST_FF("require that request path is dequoted", SamplingHandler(), HttpServer(0)) +{ + auto token = f2.repo().bind("/[foo]", f1); + EXPECT_EQUAL("[]", getPage(f2.port(), "/%5bfoo%5D")); + { + auto guard = std::lock_guard(f1.my_lock); + EXPECT_EQUAL(f1.my_path, "/[foo]"); + EXPECT_EQUAL(f1.my_params.size(), 0u); + } +} + +//----------------------------------------------------------------------------- + +TEST_FFFF("require that the state server wires the appropriate url prefixes", + SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(), + StateServer(0, f1, f2, f3)) +{ + f2.setTotalMetrics("{}"); // avoid empty result + int port = f4.getListenPort(); + EXPECT_TRUE(getFull(port, short_root_path).find("HTTP/1.1 200 OK") == 0); + EXPECT_TRUE(getFull(port, total_metrics_path).find("HTTP/1.1 200 OK") == 0); + EXPECT_TRUE(getFull(port, unknown_path).find("HTTP/1.1 404 Not Found") == 0); +} + +TEST_FFFF("require that the state server exposes the state api handler repo", + SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(), + StateServer(0, f1, f2, f3)) +{ + int port = f4.getListenPort(); + vespalib::string page1 = getPage(port, root_path); + auto token = f4.repo().add_root_resource("state/v1/custom"); + vespalib::string page2 = getPage(port, root_path); + EXPECT_NOT_EQUAL(page1, page2); + token.reset(); + vespalib::string page3 = getPage(port, root_path); + EXPECT_EQUAL(page3, page1); +} + +//----------------------------------------------------------------------------- + +TEST_FFFF("require that json handlers can be removed from repo", + DummyHandler("[1]"), DummyHandler("[2]"), DummyHandler("[3]"), + JsonHandlerRepo()) +{ + auto token1 = f4.bind("/foo", f1); + auto token2 = f4.bind("/foo/bar", f2); + auto token3 = f4.bind("/foo/bar/baz", f3); + std::map<vespalib::string,vespalib::string> params; + EXPECT_EQUAL("[1]", f4.get("", "/foo", params)); + EXPECT_EQUAL("[2]", f4.get("", "/foo/bar", params)); + EXPECT_EQUAL("[3]", f4.get("", "/foo/bar/baz", params)); + token2.reset(); + EXPECT_EQUAL("[1]", f4.get("", "/foo", params)); + EXPECT_EQUAL("[1]", f4.get("", "/foo/bar", params)); + EXPECT_EQUAL("[3]", f4.get("", "/foo/bar/baz", params)); +} + +TEST_FFFF("require that json handlers can be shadowed", + DummyHandler("[1]"), DummyHandler("[2]"), DummyHandler("[3]"), + JsonHandlerRepo()) +{ + auto token1 = f4.bind("/foo", f1); + auto token2 = f4.bind("/foo/bar", f2); + std::map<vespalib::string,vespalib::string> params; + EXPECT_EQUAL("[1]", f4.get("", "/foo", params)); + EXPECT_EQUAL("[2]", f4.get("", "/foo/bar", params)); + auto token3 = f4.bind("/foo/bar", f3); + EXPECT_EQUAL("[3]", f4.get("", "/foo/bar", params)); + token3.reset(); + EXPECT_EQUAL("[2]", f4.get("", "/foo/bar", params)); +} + +TEST_F("require that root resources can be tracked", JsonHandlerRepo()) +{ + EXPECT_TRUE(std::vector<vespalib::string>({}) == f1.get_root_resources()); + auto token1 = f1.add_root_resource("/health"); + EXPECT_TRUE(std::vector<vespalib::string>({"/health"}) == f1.get_root_resources()); + auto token2 = f1.add_root_resource("/config"); + EXPECT_TRUE(std::vector<vespalib::string>({"/health", "/config"}) == f1.get_root_resources()); + auto token3 = f1.add_root_resource("/custom/foo"); + EXPECT_TRUE(std::vector<vespalib::string>({"/health", "/config", "/custom/foo"}) == f1.get_root_resources()); + token2.reset(); + EXPECT_TRUE(std::vector<vespalib::string>({"/health", "/custom/foo"}) == f1.get_root_resources()); +} + +//----------------------------------------------------------------------------- + +TEST_FFFF("require that state api responds to the expected paths", + SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3)) +{ + f2.setTotalMetrics("{}"); // avoid empty result + EXPECT_TRUE(!f4.get(host_tag, short_root_path, empty_params).empty()); + EXPECT_TRUE(!f4.get(host_tag, root_path, empty_params).empty()); + EXPECT_TRUE(!f4.get(host_tag, health_path, empty_params).empty()); + EXPECT_TRUE(!f4.get(host_tag, metrics_path, empty_params).empty()); + EXPECT_TRUE(!f4.get(host_tag, config_path, empty_params).empty()); + EXPECT_TRUE(!f4.get(host_tag, total_metrics_path, empty_params).empty()); + EXPECT_TRUE(f4.get(host_tag, unknown_path, empty_params).empty()); + EXPECT_TRUE(f4.get(host_tag, unknown_state_path, empty_params).empty()); +} + +TEST_FFFF("require that top-level urls are generated correctly", + SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3)) +{ + EXPECT_EQUAL("{\"resources\":[" + "{\"url\":\"http://HOST/state/v1/health\"}," + "{\"url\":\"http://HOST/state/v1/metrics\"}," + "{\"url\":\"http://HOST/state/v1/config\"}]}", + f4.get(host_tag, root_path, empty_params)); + EXPECT_EQUAL(f4.get(host_tag, root_path, empty_params), + f4.get(host_tag, short_root_path, empty_params)); +} + +TEST_FFFF("require that top-level resource list can be extended", + SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3)) +{ + auto token = f4.repo().add_root_resource("/state/v1/custom"); + EXPECT_EQUAL("{\"resources\":[" + "{\"url\":\"http://HOST/state/v1/health\"}," + "{\"url\":\"http://HOST/state/v1/metrics\"}," + "{\"url\":\"http://HOST/state/v1/config\"}," + "{\"url\":\"http://HOST/state/v1/custom\"}]}", + f4.get(host_tag, root_path, empty_params)); +} + +TEST_FFFF("require that health resource works as expected", + SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3)) +{ + EXPECT_EQUAL("{\"status\":{\"code\":\"up\"}}", + f4.get(host_tag, health_path, empty_params)); + f1.setFailed("FAIL MSG"); + EXPECT_EQUAL("{\"status\":{\"code\":\"down\",\"message\":\"FAIL MSG\"}}", + f4.get(host_tag, health_path, empty_params)); +} + +TEST_FFFF("require that metrics resource works as expected", + SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3)) +{ + EXPECT_EQUAL("{\"status\":{\"code\":\"up\"}}", + f4.get(host_tag, metrics_path, empty_params)); + f1.setFailed("FAIL MSG"); + EXPECT_EQUAL("{\"status\":{\"code\":\"down\",\"message\":\"FAIL MSG\"}}", + f4.get(host_tag, metrics_path, empty_params)); + f1.setOk(); + f2.setMetrics("{\"foo\":\"bar\"}"); + EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":{\"foo\":\"bar\"}}", + f4.get(host_tag, metrics_path, empty_params)); +} + +TEST_FFFF("require that config resource works as expected", + SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3)) +{ + EXPECT_EQUAL("{\"config\":{}}", + f4.get(host_tag, config_path, empty_params)); + f3.addConfig(SimpleComponentConfigProducer::Config("foo", 3)); + EXPECT_EQUAL("{\"config\":{\"generation\":3,\"foo\":{\"generation\":3}}}", + f4.get(host_tag, config_path, empty_params)); + f3.addConfig(SimpleComponentConfigProducer::Config("foo", 4)); + f3.addConfig(SimpleComponentConfigProducer::Config("bar", 4, "error")); + EXPECT_EQUAL("{\"config\":{\"generation\":4,\"bar\":{\"generation\":4,\"message\":\"error\"},\"foo\":{\"generation\":4}}}", + f4.get(host_tag, config_path, empty_params)); + f3.removeConfig("bar"); + EXPECT_EQUAL("{\"config\":{\"generation\":4,\"foo\":{\"generation\":4}}}", + f4.get(host_tag, config_path, empty_params)); +} + +TEST_FFFF("require that state api also can return total metric", + SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3)) +{ + f2.setTotalMetrics("{\"foo\":\"bar\"}"); + EXPECT_EQUAL("{\"foo\":\"bar\"}", + f4.get(host_tag, total_metrics_path, empty_params)); +} + +TEST_FFFFF("require that custom handlers can be added to the state server", + SimpleHealthProducer(), SimpleMetricsProducer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3), DummyHandler("[123]")) +{ + EXPECT_EQUAL("", f4.get(host_tag, my_path, empty_params)); + auto token = f4.repo().bind(my_path, f5); + EXPECT_EQUAL("[123]", f4.get(host_tag, my_path, empty_params)); + token.reset(); + EXPECT_EQUAL("", f4.get(host_tag, my_path, empty_params)); +} + +struct EchoConsumer : MetricsProducer { + ~EchoConsumer() override; + vespalib::string getMetrics(const vespalib::string &consumer) override { + return "[\"" + consumer + "\"]"; + } + vespalib::string getTotalMetrics(const vespalib::string &consumer) override { + return "[\"" + consumer + "\"]"; + } +}; + +EchoConsumer::~EchoConsumer() = default; + +TEST_FFFF("require that empty v1 metrics consumer defaults to 'statereporter'", + SimpleHealthProducer(), EchoConsumer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3)) +{ + std::map<vespalib::string,vespalib::string> my_params; + EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":[\"statereporter\"]}", f4.get(host_tag, metrics_path, empty_params)); +} + +TEST_FFFF("require that empty total metrics consumer defaults to the empty string", + SimpleHealthProducer(), EchoConsumer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3)) +{ + std::map<vespalib::string,vespalib::string> my_params; + EXPECT_EQUAL("[\"\"]", f4.get(host_tag, total_metrics_path, empty_params)); +} + +TEST_FFFF("require that metrics consumer is passed correctly", + SimpleHealthProducer(), EchoConsumer(), SimpleComponentConfigProducer(), + StateApi(f1, f2, f3)) +{ + std::map<vespalib::string,vespalib::string> my_params; + my_params["consumer"] = "ME"; + EXPECT_EQUAL("{\"status\":{\"code\":\"up\"},\"metrics\":[\"ME\"]}", f4.get(host_tag, metrics_path, my_params)); + EXPECT_EQUAL("[\"ME\"]", f4.get(host_tag, total_metrics_path, my_params)); +} + +void check_json(const vespalib::string &expect_json, const vespalib::string &actual_json) { + Slime expect_slime; + Slime actual_slime; + EXPECT_TRUE(slime::JsonFormat::decode(expect_json, expect_slime) > 0); + EXPECT_TRUE(slime::JsonFormat::decode(actual_json, actual_slime) > 0); + EXPECT_EQUAL(expect_slime, actual_slime); +} + +TEST("require that generic state can be explored") { + vespalib::string json_model = + "{" + " foo: 'bar'," + " cnt: 123," + " engine: {" + " up: 'yes'," + " stats: {" + " latency: 5," + " qps: 100" + " }" + " }," + " list: {" + " one: {" + " size: {" + " value: 1" + " }" + " }," + " two: {" + " size: 2" + " }" + " }" + "}"; + vespalib::string json_root = + "{" + " full: true," + " foo: 'bar'," + " cnt: 123," + " engine: {" + " up: 'yes'," + " url: 'http://HOST/state/v1/engine'" + " }," + " list: {" + " one: {" + " size: {" + " value: 1," + " url: 'http://HOST/state/v1/list/one/size'" + " }" + " }," + " two: {" + " size: 2," + " url: 'http://HOST/state/v1/list/two'" + " }" + " }" + "}"; + vespalib::string json_engine = + "{" + " full: true," + " up: 'yes'," + " stats: {" + " latency: 5," + " qps: 100," + " url: 'http://HOST/state/v1/engine/stats'" + " }" + "}"; + vespalib::string json_engine_stats = + "{" + " full: true," + " latency: 5," + " qps: 100" + "}"; + vespalib::string json_list = + "{" + " one: {" + " size: {" + " value: 1," + " url: 'http://HOST/state/v1/list/one/size'" + " }" + " }," + " two: {" + " size: 2," + " url: 'http://HOST/state/v1/list/two'" + " }" + "}"; + vespalib::string json_list_one = + "{" + " size: {" + " value: 1," + " url: 'http://HOST/state/v1/list/one/size'" + " }" + "}"; + vespalib::string json_list_one_size = "{ full: true, value: 1 }"; + vespalib::string json_list_two = "{ full: true, size: 2 }"; + //------------------------------------------------------------------------- + Slime slime_state; + EXPECT_TRUE(slime::JsonFormat::decode(json_model, slime_state) > 0); + SlimeExplorer slime_explorer(slime_state.get()); + GenericStateHandler state_handler(short_root_path, slime_explorer); + EXPECT_EQUAL("", state_handler.get(host_tag, unknown_path, empty_params)); + EXPECT_EQUAL("", state_handler.get(host_tag, unknown_state_path, empty_params)); + check_json(json_root, state_handler.get(host_tag, root_path, empty_params)); + check_json(json_engine, state_handler.get(host_tag, root_path + "engine", empty_params)); + check_json(json_engine_stats, state_handler.get(host_tag, root_path + "engine/stats", empty_params)); + check_json(json_list, state_handler.get(host_tag, root_path + "list", empty_params)); + check_json(json_list_one, state_handler.get(host_tag, root_path + "list/one", empty_params)); + check_json(json_list_one_size, state_handler.get(host_tag, root_path + "list/one/size", empty_params)); + check_json(json_list_two, state_handler.get(host_tag, root_path + "list/two", empty_params)); +} + +TEST_MAIN() { + mkdir("var", S_IRWXU); + mkdir("var/run", S_IRWXU); + TEST_RUN_ALL(); + rmdir("var/run"); + rmdir("var"); +} diff --git a/vespalib/src/tests/trace/CMakeLists.txt b/vespalib/src/tests/trace/CMakeLists.txt index a632d419b4b..76cb266f230 100644 --- a/vespalib/src/tests/trace/CMakeLists.txt +++ b/vespalib/src/tests/trace/CMakeLists.txt @@ -7,10 +7,10 @@ vespa_add_executable(vespalib_trace_test_app TEST ) vespa_add_test(NAME vespalib_trace_test_app COMMAND vespalib_trace_test_app) -vespa_add_executable(staging_vespalib_trace_serialization_test_app TEST +vespa_add_executable(vespalib_trace_serialization_test_app TEST SOURCES trace_serialization.cpp DEPENDS vespalib ) -vespa_add_test(NAME staging_vespalib_trace_serialization_test_app COMMAND staging_vespalib_trace_serialization_test_app) +vespa_add_test(NAME vespalib_trace_serialization_test_app COMMAND vespalib_trace_serialization_test_app) |