/*
- * Copyright 2017 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
#include <folly/io/IOBuf.h>
#include <folly/Format.h>
#include <folly/Range.h>
#include <folly/io/Cursor.h>
#include <folly/portability/GTest.h>
+#include <numeric>
+#include <vector>
using folly::ByteRange;
using folly::format;
c.write((uint8_t)40); // OK
try {
c.write((uint8_t)10); // Bad write, checked should except.
- EXPECT_EQ(true, false);
+ ADD_FAILURE();
} catch (...) {
}
}
return str;
}
-} // namespace
+} // namespace
TEST(IOBuf, PullAndPeek) {
std::unique_ptr<IOBuf> iobuf1(IOBuf::create(10));
EXPECT_EQ(1, rcursor2.readBE<uint64_t>());
EXPECT_EQ(10, rcursor2.readBE<uint64_t>());
EXPECT_EQ(20, rcursor2.readBE<uint32_t>());
-
}
TEST(IOBuf, Gather) {
cursor.insert(std::move(cloned));
cursor.insert(folly::IOBuf::create(0));
+ EXPECT_EQ(4, cursor.getCurrentPosition());
EXPECT_EQ(7, iobuf1->countChainElements());
EXPECT_EQ(14, iobuf1->computeChainDataLength());
// Check that nextBuf got set correctly to the buffer with 1 byte left
cursor.skip(1);
cursor.insert(std::move(cloned));
+ EXPECT_EQ(2, cursor.getCurrentPosition());
EXPECT_EQ(8, iobuf1->countChainElements());
EXPECT_EQ(15, iobuf1->computeChainDataLength());
// Check that nextBuf got set correctly
cursor.read<uint8_t>();
}
{
- // Check that inserting at the beginning doesn't create empty buf
+ // Check that inserting at the beginning of a chunk (except first one)
+ // doesn't create empty buf
RWPrivateCursor cursor(iobuf1.get());
Cursor(iobuf1.get()).clone(cloned, 1);
EXPECT_EQ(1, cloned->countChainElements());
EXPECT_EQ(1, cloned->computeChainDataLength());
+ cursor.skip(1);
+
cursor.insert(std::move(cloned));
+ EXPECT_EQ(2, cursor.getCurrentPosition());
+ EXPECT_EQ(14, cursor.totalLength());
EXPECT_EQ(9, iobuf1->countChainElements());
EXPECT_EQ(16, iobuf1->computeChainDataLength());
// Check that nextBuf got set correctly
cursor.read<uint8_t>();
}
+ {
+ // Check that inserting at the beginning of a chain DOES keep an empty
+ // buffer.
+ RWPrivateCursor cursor(iobuf1.get());
+ Cursor(iobuf1.get()).clone(cloned, 1);
+ EXPECT_EQ(1, cloned->countChainElements());
+ EXPECT_EQ(1, cloned->computeChainDataLength());
+
+ cursor.insert(std::move(cloned));
+ EXPECT_EQ(1, cursor.getCurrentPosition());
+ EXPECT_EQ(16, cursor.totalLength());
+ EXPECT_EQ(11, iobuf1->countChainElements());
+ EXPECT_EQ(17, iobuf1->computeChainDataLength());
+ // Check that nextBuf got set correctly
+ cursor.read<uint8_t>();
+ }
}
TEST(IOBuf, cloneWithEmptyBufAtStart) {
EXPECT_THROW({cursor.readBE<uint32_t>();}, std::out_of_range);
}
+TEST(IOBuf, QueueAppenderPushAtMostFillBuffer) {
+ folly::IOBufQueue queue;
+ // There should be a goodMallocSize between 125 and 1000
+ QueueAppender appender{&queue, 125};
+ std::vector<uint8_t> data;
+ data.resize(1000);
+ std::iota(data.begin(), data.end(), uint8_t(0));
+ // Add 100 byte
+ appender.pushAtMost(data.data(), 100);
+ // Add 900 bytes
+ appender.pushAtMost(data.data() + 100, data.size() - 100);
+ const auto buf = queue.front();
+ // Should fill the current buffer before adding another
+ EXPECT_LE(2, buf->countChainElements());
+ EXPECT_EQ(0, buf->tailroom());
+ EXPECT_LE(125, buf->length());
+ EXPECT_EQ(1000, buf->computeChainDataLength());
+ const StringPiece sp{(const char*)data.data(), data.size()};
+ EXPECT_EQ(sp, toString(*buf));
+}
+
+TEST(IOBuf, QueueAppenderInsertOwn) {
+ auto buf = IOBuf::create(10);
+ folly::IOBufQueue queue;
+ QueueAppender appender{&queue, 128};
+ appender.insert(std::move(buf));
+
+ std::vector<uint8_t> data;
+ data.resize(256);
+ std::iota(data.begin(), data.end(), 0);
+ appender.pushAtMost(folly::range(data));
+ // Buffer is owned, so we should write to it
+ EXPECT_LE(2, queue.front()->countChainElements());
+ EXPECT_EQ(0, queue.front()->tailroom());
+ const StringPiece sp{(const char*)data.data(), data.size()};
+ EXPECT_EQ(sp, toString(*queue.front()));
+}
+
+TEST(IOBuf, QueueAppenderInsertClone) {
+ IOBuf buf{IOBuf::CREATE, 100};
+ folly::IOBufQueue queue;
+ QueueAppender appender{&queue, 100};
+ // Buffer is shared, so we create a new buffer to write to
+ appender.insert(buf);
+ uint8_t x = 42;
+ appender.pushAtMost(&x, 1);
+ EXPECT_EQ(2, queue.front()->countChainElements());
+ EXPECT_EQ(0, queue.front()->length());
+ EXPECT_LT(0, queue.front()->tailroom());
+ EXPECT_EQ(1, queue.front()->next()->length());
+ EXPECT_EQ(x, queue.front()->next()->data()[0]);
+}
+
TEST(IOBuf, CursorOperators) {
// Test operators on a single-item chain
{
EXPECT_EQ(retreated.totalLength(), 5);
EXPECT_EQ(curs.totalLength(), 0);
}
+
+TEST(IOBuf, tryRead) {
+ unique_ptr<IOBuf> iobuf1(IOBuf::create(6));
+ iobuf1->append(6);
+ unique_ptr<IOBuf> iobuf2(IOBuf::create(24));
+ iobuf2->append(24);
+
+ iobuf1->prependChain(std::move(iobuf2));
+
+ EXPECT_TRUE(iobuf1->isChained());
+
+ RWPrivateCursor wcursor(iobuf1.get());
+ Cursor rcursor(iobuf1.get());
+ wcursor.writeLE((uint32_t)1);
+ wcursor.writeLE((uint64_t)1);
+ wcursor.writeLE((uint64_t)1);
+ wcursor.writeLE((uint64_t)1);
+ wcursor.writeLE((uint16_t)1);
+ EXPECT_EQ(0, wcursor.totalLength());
+
+ EXPECT_EQ(1u, rcursor.readLE<uint32_t>());
+
+ EXPECT_EQ(1u, rcursor.readLE<uint32_t>());
+ EXPECT_EQ(0u, rcursor.readLE<uint32_t>());
+
+ EXPECT_EQ(1u, rcursor.readLE<uint32_t>());
+ rcursor.skip(4);
+
+ uint32_t val;
+ EXPECT_TRUE(rcursor.tryRead(val));
+ EXPECT_EQ(1, val);
+ EXPECT_TRUE(rcursor.tryRead(val));
+
+ EXPECT_EQ(0, val);
+ EXPECT_FALSE(rcursor.tryRead(val));
+}
+
+TEST(IOBuf, tryReadLE) {
+ IOBuf buf{IOBuf::CREATE, 4};
+ buf.append(4);
+
+ RWPrivateCursor wcursor(&buf);
+ Cursor rcursor(&buf);
+
+ const uint32_t expected = 0x01020304;
+ wcursor.writeLE(expected);
+ uint32_t actual;
+ EXPECT_TRUE(rcursor.tryReadLE(actual));
+ EXPECT_EQ(expected, actual);
+}
+
+TEST(IOBuf, tryReadBE) {
+ IOBuf buf{IOBuf::CREATE, 4};
+ buf.append(4);
+
+ RWPrivateCursor wcursor(&buf);
+ Cursor rcursor(&buf);
+
+ const uint32_t expected = 0x01020304;
+ wcursor.writeBE(expected);
+ uint32_t actual;
+ EXPECT_TRUE(rcursor.tryReadBE(actual));
+ EXPECT_EQ(expected, actual);
+}
+
+TEST(IOBuf, tryReadConsumesAllInputOnFailure) {
+ IOBuf buf{IOBuf::CREATE, 2};
+ buf.append(2);
+
+ Cursor rcursor(&buf);
+ uint32_t val;
+ EXPECT_FALSE(rcursor.tryRead(val));
+ EXPECT_EQ(0, rcursor.totalLength());
+}
+
+TEST(IOBuf, readConsumesAllInputOnFailure) {
+ IOBuf buf{IOBuf::CREATE, 2};
+ buf.append(2);
+
+ Cursor rcursor(&buf);
+ EXPECT_THROW(rcursor.read<uint32_t>(), std::out_of_range);
+ EXPECT_EQ(0, rcursor.totalLength());
+}
+
+TEST(IOBuf, pushEmptyByteRange) {
+ // Test pushing an empty ByteRange. This mainly tests that we do not
+ // trigger UBSAN warnings by calling memcpy() with an null source pointer,
+ // which is undefined behavior even if the length is 0.
+ IOBuf buf{IOBuf::CREATE, 2};
+ ByteRange emptyBytes;
+
+ // Test calling Cursor::push()
+ RWPrivateCursor wcursor(&buf);
+ wcursor.push(emptyBytes);
+ EXPECT_EQ(0, buf.computeChainDataLength());
+
+ // Test calling Appender::push()
+ Appender app(&buf, 16);
+ app.push(emptyBytes);
+ EXPECT_EQ(0, buf.computeChainDataLength());
+}
+
+TEST(IOBuf, positionTracking) {
+ unique_ptr<IOBuf> iobuf1(IOBuf::create(6));
+ iobuf1->append(6);
+ unique_ptr<IOBuf> iobuf2(IOBuf::create(24));
+ iobuf2->append(24);
+ iobuf1->prependChain(std::move(iobuf2));
+
+ Cursor cursor(iobuf1.get());
+
+ EXPECT_EQ(0, cursor.getCurrentPosition());
+ EXPECT_EQ(6, cursor.length());
+
+ cursor.skip(3);
+ EXPECT_EQ(3, cursor.getCurrentPosition());
+ EXPECT_EQ(3, cursor.length());
+
+ // Test that we properly handle advancing to the next chunk.
+ cursor.skip(4);
+ EXPECT_EQ(7, cursor.getCurrentPosition());
+ EXPECT_EQ(23, cursor.length());
+
+ // Test that we properly handle doing to the previous chunk.
+ cursor.retreat(2);
+ EXPECT_EQ(5, cursor.getCurrentPosition());
+ EXPECT_EQ(1, cursor.length());
+
+ // Test that we properly handle advanceToEnd
+ cursor.advanceToEnd();
+ EXPECT_EQ(30, cursor.getCurrentPosition());
+ EXPECT_EQ(0, cursor.totalLength());
+
+ // Reset to 0.
+ cursor.reset(iobuf1.get());
+ EXPECT_EQ(0, cursor.getCurrentPosition());
+ EXPECT_EQ(30, cursor.totalLength());
+}