X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Fio%2Ftest%2FIOBufTest.cpp;h=e7649d832fd34347919e252a9912cef5ddd1dc47;hb=cd1bdc912603c0358ba733d379a74ae90ab3a437;hp=baab1c854e0b6e02177f38f391819aff3ee8ca8b;hpb=ab4ee5fa6242d9686a93e2ef5e813c0fce65d2b2;p=folly.git diff --git a/folly/io/test/IOBufTest.cpp b/folly/io/test/IOBufTest.cpp index baab1c85..e7649d83 100644 --- a/folly/io/test/IOBufTest.cpp +++ b/folly/io/test/IOBufTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2013 Facebook, Inc. + * Copyright 2013-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,16 @@ * limitations under the License. */ -#include "folly/io/IOBuf.h" -#include "folly/io/TypedIOBuf.h" +#include +#include -// googletest requires std::tr1::tuple, not std::tuple -#include +#include -#include #include -#include -#include "folly/Malloc.h" -#include "folly/Range.h" +#include +#include +#include using folly::fbstring; using folly::fbvector; @@ -142,7 +140,26 @@ TEST(IOBuf, TakeOwnership) { iobuf3.reset(); EXPECT_EQ(1, deleteCount); - + deleteCount = 0; + { + uint32_t size4 = 1234; + uint8_t *buf4 = new uint8_t[size4]; + uint32_t length4 = 48; + IOBuf iobuf4(IOBuf::TAKE_OWNERSHIP, buf4, size4, length4, + deleteArrayBuffer, &deleteCount); + EXPECT_EQ(buf4, iobuf4.data()); + EXPECT_EQ(length4, iobuf4.length()); + EXPECT_EQ(buf4, iobuf4.buffer()); + EXPECT_EQ(size4, iobuf4.capacity()); + + IOBuf iobuf5 = std::move(iobuf4); + EXPECT_EQ(buf4, iobuf5.data()); + EXPECT_EQ(length4, iobuf5.length()); + EXPECT_EQ(buf4, iobuf5.buffer()); + EXPECT_EQ(size4, iobuf5.capacity()); + EXPECT_EQ(0, deleteCount); + } + EXPECT_EQ(1, deleteCount); } TEST(IOBuf, WrapBuffer) { @@ -161,6 +178,22 @@ TEST(IOBuf, WrapBuffer) { EXPECT_EQ(size2, iobuf2->length()); EXPECT_EQ(buf2.get(), iobuf2->buffer()); EXPECT_EQ(size2, iobuf2->capacity()); + + uint32_t size3 = 4321; + unique_ptr buf3(new uint8_t[size3]); + IOBuf iobuf3(IOBuf::WRAP_BUFFER, buf3.get(), size3); + EXPECT_EQ(buf3.get(), iobuf3.data()); + EXPECT_EQ(size3, iobuf3.length()); + EXPECT_EQ(buf3.get(), iobuf3.buffer()); + EXPECT_EQ(size3, iobuf3.capacity()); + + const uint32_t size4 = 2345; + unique_ptr buf4(new uint8_t[size4]); + IOBuf iobuf4 = IOBuf::wrapBufferAsValue(buf4.get(), size4); + EXPECT_EQ(buf4.get(), iobuf4.data()); + EXPECT_EQ(size4, iobuf4.length()); + EXPECT_EQ(buf4.get(), iobuf4.buffer()); + EXPECT_EQ(size4, iobuf4.capacity()); } TEST(IOBuf, CreateCombined) { @@ -498,6 +531,15 @@ TEST(IOBuf, Chaining) { gen.seed(fillSeed); checkChain(chainClone.get(), gen); + // cloneCoalesced + { + auto chainCloneCoalesced = chainClone->cloneCoalesced(); + EXPECT_EQ(1, chainCloneCoalesced->countChainElements()); + EXPECT_EQ(fullLength, chainCloneCoalesced->computeChainDataLength()); + gen.seed(fillSeed); + checkChain(chainCloneCoalesced.get(), gen); + } + // Coalesce the entire chain chainClone->coalesce(); EXPECT_EQ(1, chainClone->countChainElements()); @@ -602,7 +644,6 @@ TEST(IOBuf, Reserve) { EXPECT_EQ(0, iob->headroom()); EXPECT_EQ(100, iob->length()); const void* p1 = iob->buffer(); - const uint8_t* d1 = iob->data(); iob->reserve(100, 2512); // allocation sizes are multiples of 256 EXPECT_LE(100, iob->headroom()); if (folly::usingJEMalloc()) { @@ -660,6 +701,13 @@ TEST(IOBuf, copyBuffer) { EXPECT_EQ(3, buf->headroom()); EXPECT_EQ(0, buf->length()); EXPECT_LE(6, buf->tailroom()); + + // A stack-allocated version + IOBuf stackBuf(IOBuf::COPY_BUFFER, s, 1, 2); + EXPECT_EQ(1, stackBuf.headroom()); + EXPECT_EQ(s, std::string(reinterpret_cast(stackBuf.data()), + stackBuf.length())); + EXPECT_LE(2, stackBuf.tailroom()); } TEST(IOBuf, maybeCopyBuffer) { @@ -678,6 +726,11 @@ TEST(IOBuf, maybeCopyBuffer) { EXPECT_EQ(nullptr, buf.get()); } +TEST(IOBuf, copyEmptyBuffer) { + auto buf = IOBuf::copyBuffer(nullptr, 0); + EXPECT_EQ(buf->length(), 0); +} + namespace { int customDeleterCount = 0; @@ -702,7 +755,7 @@ void customDeleteArray(OwnershipTestClass* p) { delete[] p; } -} // namespace +} // namespace TEST(IOBuf, takeOwnershipUniquePtr) { destructorCount = 0; @@ -761,11 +814,7 @@ TEST(IOBuf, takeOwnershipUniquePtr) { } TEST(IOBuf, Alignment) { - // max_align_t doesn't exist in gcc 4.6.2 - struct MaxAlign { - char c; - } __attribute__((aligned)); - size_t alignment = alignof(MaxAlign); + size_t alignment = alignof(std::max_align_t); std::vector sizes {0, 1, 64, 256, 1024, 1 << 10}; for (size_t size : sizes) { @@ -801,8 +850,12 @@ enum BufType { class MoveToFbStringTest : public ::testing::TestWithParam> { protected: - void SetUp() { - std::tr1::tie(elementSize_, elementCount_, shared_, type_) = GetParam(); + void SetUp() override { + elementSize_ = std::tr1::get<0>(GetParam()); + elementCount_ = std::tr1::get<1>(GetParam()); + shared_ = std::tr1::get<2>(GetParam()); + type_ = std::tr1::get<3>(GetParam()); + buf_ = makeBuf(); for (int i = 0; i < elementCount_ - 1; ++i) { buf_->prependChain(makeBuf()); @@ -844,7 +897,6 @@ class MoveToFbStringTest } default: throw std::invalid_argument("unexpected buffer type parameter"); - break; } memset(buf->writableData(), 'x', elementSize_); return buf; @@ -929,11 +981,416 @@ TEST(IOBuf, getIov) { buf->prev()->clear(); iov = buf->getIov(); EXPECT_EQ(count - 3, iov.size()); + + // test appending to an existing iovec array + iov.clear(); + const char localBuf[] = "hello"; + iov.push_back({(void*)localBuf, sizeof(localBuf)}); + iov.push_back({(void*)localBuf, sizeof(localBuf)}); + buf->appendToIov(&iov); + EXPECT_EQ(count - 1, iov.size()); + EXPECT_EQ(localBuf, iov[0].iov_base); + EXPECT_EQ(localBuf, iov[1].iov_base); + // The first two IOBufs were cleared, so the next iov entry + // should be the third IOBuf in the chain. + EXPECT_EQ(buf->next()->next()->data(), iov[2].iov_base); +} + +TEST(IOBuf, move) { + // Default allocate an IOBuf on the stack + IOBuf outerBuf; + char data[] = "foobar"; + uint32_t length = sizeof(data); + uint32_t actualCapacity{0}; + const void* ptr{nullptr}; + + { + // Create a small IOBuf on the stack. + // Note that IOBufs created on the stack always use an external buffer. + IOBuf b1(IOBuf::CREATE, 10); + actualCapacity = b1.capacity(); + EXPECT_GE(actualCapacity, 10); + EXPECT_EQ(0, b1.length()); + EXPECT_FALSE(b1.isShared()); + ptr = b1.data(); + ASSERT_TRUE(ptr != nullptr); + memcpy(b1.writableTail(), data, length); + b1.append(length); + EXPECT_EQ(length, b1.length()); + + // Use the move constructor + IOBuf b2(std::move(b1)); + EXPECT_EQ(ptr, b2.data()); + EXPECT_EQ(length, b2.length()); + EXPECT_EQ(actualCapacity, b2.capacity()); + EXPECT_FALSE(b2.isShared()); + + // Use the move assignment operator + outerBuf = std::move(b2); + // Close scope, destroying b1 and b2 + // (which are both be invalid now anyway after moving out of them) + } + + EXPECT_EQ(ptr, outerBuf.data()); + EXPECT_EQ(length, outerBuf.length()); + EXPECT_EQ(actualCapacity, outerBuf.capacity()); + EXPECT_FALSE(outerBuf.isShared()); +} + +namespace { +std::unique_ptr fromStr(StringPiece sp) { + return IOBuf::copyBuffer(ByteRange(sp)); +} +} // namespace + +TEST(IOBuf, HashAndEqual) { + folly::IOBufEqual eq; + folly::IOBufHash hash; + + EXPECT_TRUE(eq(nullptr, nullptr)); + EXPECT_EQ(0, hash(nullptr)); + + auto empty = IOBuf::create(0); + + EXPECT_TRUE(eq(*empty, *empty)); + EXPECT_TRUE(eq(empty, empty)); + + EXPECT_FALSE(eq(nullptr, empty)); + EXPECT_FALSE(eq(empty, nullptr)); + + EXPECT_EQ(hash(*empty), hash(empty)); + EXPECT_NE(0, hash(empty)); + + auto a = fromStr("hello"); + + EXPECT_TRUE(eq(*a, *a)); + EXPECT_TRUE(eq(a, a)); + + EXPECT_FALSE(eq(nullptr, a)); + EXPECT_FALSE(eq(a, nullptr)); + + EXPECT_EQ(hash(*a), hash(a)); + EXPECT_NE(0, hash(a)); + + auto b = fromStr("hello"); + + EXPECT_TRUE(eq(*a, *b)); + EXPECT_TRUE(eq(a, b)); + + EXPECT_EQ(hash(a), hash(b)); + + auto c = fromStr("hellow"); + + EXPECT_FALSE(eq(a, c)); + EXPECT_NE(hash(a), hash(c)); + + auto d = fromStr("world"); + + EXPECT_FALSE(eq(a, d)); + EXPECT_NE(hash(a), hash(d)); + + auto e = fromStr("helloworld"); + auto f = fromStr("hello"); + f->prependChain(fromStr("wo")); + f->prependChain(fromStr("rld")); + + EXPECT_TRUE(eq(e, f)); + EXPECT_EQ(hash(e), hash(f)); +} + +// reserveSlow() had a bug when reallocating the buffer in place. It would +// preserve old headroom if it's not too much (heuristically) but wouldn't +// adjust the requested amount of memory to account for that; the end result +// would be that reserve() would return with less tailroom than requested. +TEST(IOBuf, ReserveWithHeadroom) { + // This is assuming jemalloc, where we know that 4096 and 8192 bytes are + // valid (and consecutive) allocation sizes. We're hoping that our + // 4096-byte buffer can be expanded in place to 8192 (in practice, this + // usually happens). + const char data[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"; + constexpr size_t reservedSize = 24; // sizeof(SharedInfo) + // chosen carefully so that the buffer is exactly 4096 bytes + IOBuf buf(IOBuf::CREATE, 4096 - reservedSize); + buf.advance(10); + memcpy(buf.writableData(), data, sizeof(data)); + buf.append(sizeof(data)); + EXPECT_EQ(sizeof(data), buf.length()); + + // Grow the buffer (hopefully in place); this would incorrectly reserve + // the 10 bytes of headroom, giving us 10 bytes less than requested. + size_t tailroom = 8192 - reservedSize - sizeof(data); + buf.reserve(0, tailroom); + EXPECT_LE(tailroom, buf.tailroom()); + EXPECT_EQ(sizeof(data), buf.length()); + EXPECT_EQ(0, memcmp(data, buf.data(), sizeof(data))); +} + +TEST(IOBuf, CopyConstructorAndAssignmentOperator) { + auto buf = IOBuf::create(4096); + append(buf, "hello world"); + auto buf2 = IOBuf::create(4096); + append(buf2, " goodbye"); + buf->prependChain(std::move(buf2)); + EXPECT_FALSE(buf->isShared()); + + { + auto copy = *buf; + EXPECT_TRUE(buf->isShared()); + EXPECT_TRUE(copy.isShared()); + EXPECT_EQ((void*)buf->data(), (void*)copy.data()); + EXPECT_NE(buf->next(), copy.next()); // actually different buffers + + auto copy2 = *buf; + copy2.coalesce(); + EXPECT_TRUE(buf->isShared()); + EXPECT_TRUE(copy.isShared()); + EXPECT_FALSE(copy2.isShared()); + + auto p = reinterpret_cast(copy2.data()); + EXPECT_EQ("hello world goodbye", std::string(p, copy2.length())); + } + + EXPECT_FALSE(buf->isShared()); + + { + folly::IOBuf newBuf(folly::IOBuf::CREATE, 4096); + EXPECT_FALSE(newBuf.isShared()); + + auto newBufCopy = newBuf; + EXPECT_TRUE(newBuf.isShared()); + EXPECT_TRUE(newBufCopy.isShared()); + + newBufCopy = *buf; + EXPECT_TRUE(buf->isShared()); + EXPECT_FALSE(newBuf.isShared()); + EXPECT_TRUE(newBufCopy.isShared()); + } + + EXPECT_FALSE(buf->isShared()); } -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - google::ParseCommandLineFlags(&argc, &argv, true); +TEST(IOBuf, CloneAsValue) { + auto buf = IOBuf::create(4096); + append(buf, "hello world"); + { + auto buf2 = IOBuf::create(4096); + append(buf2, " goodbye"); + buf->prependChain(std::move(buf2)); + EXPECT_FALSE(buf->isShared()); + } + + { + auto copy = buf->cloneOneAsValue(); + EXPECT_TRUE(buf->isShared()); + EXPECT_TRUE(copy.isShared()); + EXPECT_EQ((void*)buf->data(), (void*)copy.data()); + EXPECT_TRUE(buf->isChained()); + EXPECT_FALSE(copy.isChained()); + + auto copy2 = buf->cloneAsValue(); + EXPECT_TRUE(buf->isShared()); + EXPECT_TRUE(copy.isShared()); + EXPECT_TRUE(copy2.isShared()); + EXPECT_TRUE(buf->isChained()); + EXPECT_TRUE(copy2.isChained()); + + copy.unshareOne(); + EXPECT_TRUE(buf->isShared()); + EXPECT_FALSE(copy.isShared()); + EXPECT_NE((void*)buf->data(), (void*)copy.data()); + EXPECT_TRUE(copy2.isShared()); + + auto p = reinterpret_cast(copy.data()); + EXPECT_EQ("hello world", std::string(p, copy.length())); + + copy2.coalesce(); + EXPECT_FALSE(buf->isShared()); + EXPECT_FALSE(copy.isShared()); + EXPECT_FALSE(copy2.isShared()); + EXPECT_FALSE(copy2.isChained()); + + auto p2 = reinterpret_cast(copy2.data()); + EXPECT_EQ("hello world goodbye", std::string(p2, copy2.length())); + } + + EXPECT_FALSE(buf->isShared()); +} + +namespace { +// Use with string literals only +std::unique_ptr wrap(const char* str) { + return IOBuf::wrapBuffer(str, strlen(str)); +} + +std::unique_ptr copy(const char* str) { + // At least 1KiB of tailroom, to ensure an external buffer + return IOBuf::copyBuffer(str, strlen(str), 0, 1024); +} + +std::string toString(const folly::IOBuf& buf) { + std::string result; + result.reserve(buf.computeChainDataLength()); + for (auto& b : buf) { + result.append(reinterpret_cast(b.data()), b.size()); + } + return result; +} - return RUN_ALL_TESTS(); +char* writableStr(folly::IOBuf& buf) { + return reinterpret_cast(buf.writableData()); +} + +} // namespace + +TEST(IOBuf, ExternallyShared) { + struct Item { + Item(const char* src, size_t len) : size(len) { + CHECK_LE(len, sizeof(buffer)); + memcpy(buffer, src, len); + } + uint32_t refcount{0}; + uint8_t size; + char buffer[256]; + }; + + auto hello = "hello"; + struct Item it(hello, strlen(hello)); + + { + auto freeFn = [](void* /* unused */, void* userData) { + auto it = static_cast(userData); + it->refcount--; + }; + it.refcount++; + auto buf1 = IOBuf::takeOwnership(it.buffer, it.size, freeFn, &it); + EXPECT_TRUE(buf1->isManagedOne()); + EXPECT_FALSE(buf1->isSharedOne()); + + buf1->markExternallyShared(); + EXPECT_TRUE(buf1->isSharedOne()); + + { + auto buf2 = buf1->clone(); + EXPECT_TRUE(buf2->isManagedOne()); + EXPECT_TRUE(buf2->isSharedOne()); + EXPECT_EQ(buf1->data(), buf2->data()); + EXPECT_EQ(it.refcount, 1); + } + EXPECT_EQ(it.refcount, 1); + } + EXPECT_EQ(it.refcount, 0); +} + +TEST(IOBuf, Managed) { + auto hello = "hello"; + auto buf1UP = wrap(hello); + auto buf1 = buf1UP.get(); + EXPECT_FALSE(buf1->isManagedOne()); + auto buf2UP = copy("world"); + auto buf2 = buf2UP.get(); + EXPECT_TRUE(buf2->isManagedOne()); + auto buf3UP = wrap(hello); + auto buf3 = buf3UP.get(); + auto buf4UP = buf2->clone(); + auto buf4 = buf4UP.get(); + + // buf1 and buf3 share the same memory (but are unmanaged) + EXPECT_FALSE(buf1->isManagedOne()); + EXPECT_FALSE(buf3->isManagedOne()); + EXPECT_TRUE(buf1->isSharedOne()); + EXPECT_TRUE(buf3->isSharedOne()); + EXPECT_EQ(buf1->data(), buf3->data()); + + // buf2 and buf4 share the same memory (but are managed) + EXPECT_TRUE(buf2->isManagedOne()); + EXPECT_TRUE(buf4->isManagedOne()); + EXPECT_TRUE(buf2->isSharedOne()); + EXPECT_TRUE(buf4->isSharedOne()); + EXPECT_EQ(buf2->data(), buf4->data()); + + buf1->prependChain(std::move(buf2UP)); + buf1->prependChain(std::move(buf3UP)); + buf1->prependChain(std::move(buf4UP)); + + EXPECT_EQ("helloworldhelloworld", toString(*buf1)); + EXPECT_FALSE(buf1->isManaged()); + + buf1->makeManaged(); + EXPECT_TRUE(buf1->isManaged()); + + // buf1 and buf3 are now unshared (because they were unmanaged) + EXPECT_TRUE(buf1->isManagedOne()); + EXPECT_TRUE(buf3->isManagedOne()); + EXPECT_FALSE(buf1->isSharedOne()); + EXPECT_FALSE(buf3->isSharedOne()); + EXPECT_NE(buf1->data(), buf3->data()); + + // buf2 and buf4 are still shared + EXPECT_TRUE(buf2->isManagedOne()); + EXPECT_TRUE(buf4->isManagedOne()); + EXPECT_TRUE(buf2->isSharedOne()); + EXPECT_TRUE(buf4->isSharedOne()); + EXPECT_EQ(buf2->data(), buf4->data()); + + // And verify that the truth is what we expect: modify a byte in buf1 and + // buf2, see that the change from buf1 is *not* reflected in buf3, but the + // change from buf2 is reflected in buf4. + writableStr(*buf1)[0] = 'j'; + writableStr(*buf2)[0] = 'x'; + EXPECT_EQ("jelloxorldhelloxorld", toString(*buf1)); +} + +TEST(IOBuf, CoalesceEmptyBuffers) { + auto b1 = IOBuf::takeOwnership(nullptr, 0); + auto b2 = fromStr("hello"); + auto b3 = IOBuf::takeOwnership(nullptr, 0); + + b2->appendChain(std::move(b3)); + b1->appendChain(std::move(b2)); + + auto br = b1->coalesce(); + + EXPECT_TRUE(ByteRange(StringPiece("hello")) == br); +} + +TEST(IOBuf, CloneCoalescedChain) { + auto b = IOBuf::createChain(1000, 100); + b->advance(10); + const uint32_t fillSeed = 0x12345678; + boost::mt19937 gen(fillSeed); + { + auto c = b.get(); + uint64_t length = c->tailroom(); + do { + length = std::min(length, c->tailroom()); + c->append(length--); + fillBuf(c, gen); + c = c->next(); + } while (c != b.get()); + } + auto c = b->cloneCoalescedAsValue(); + EXPECT_FALSE(c.isChained()); // Not chained + EXPECT_FALSE(c.isSharedOne()); // Not shared + EXPECT_EQ(b->headroom(), c.headroom()); // Preserves headroom + EXPECT_LE(b->prev()->tailroom(), c.tailroom()); // Preserves minimum tailroom + EXPECT_EQ(b->computeChainDataLength(), c.length()); // Same length + gen.seed(fillSeed); + checkBuf(&c, gen); // Same contents +} + +TEST(IOBuf, CloneCoalescedSingle) { + auto b = IOBuf::create(1000); + b->advance(10); + b->append(900); + const uint32_t fillSeed = 0x12345678; + boost::mt19937 gen(fillSeed); + fillBuf(b.get(), gen); + + auto c = b->cloneCoalesced(); + EXPECT_FALSE(c->isChained()); // Not chained + EXPECT_TRUE(c->isSharedOne()); // Shared + EXPECT_EQ(b->buffer(), c->buffer()); + EXPECT_EQ(b->capacity(), c->capacity()); + EXPECT_EQ(b->data(), c->data()); + EXPECT_EQ(b->length(), c->length()); }