/*
- * Copyright 2016 Facebook, Inc.
+ * Copyright 2017 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <folly/Format.h>
#include <folly/Range.h>
#include <folly/io/Cursor.h>
-#include <folly/io/Cursor-defs.h>
-
-#include <gtest/gtest.h>
+#include <folly/portability/GTest.h>
+#include <numeric>
+#include <vector>
using folly::ByteRange;
using folly::format;
wcursor.writeLE((uint64_t)1);
wcursor.write((uint8_t)1);
- EXPECT_EQ(1, rcursor.readLE<uint64_t>());
+ EXPECT_EQ(1u, rcursor.readLE<uint64_t>());
rcursor.skip(8);
- EXPECT_EQ(1, rcursor.readLE<uint32_t>());
+ EXPECT_EQ(1u, rcursor.readLE<uint32_t>());
rcursor.skip(0);
- EXPECT_EQ(0, rcursor.read<uint8_t>());
- EXPECT_EQ(0, rcursor.read<uint8_t>());
- EXPECT_EQ(0, rcursor.read<uint8_t>());
- EXPECT_EQ(0, rcursor.read<uint8_t>());
- EXPECT_EQ(1, rcursor.read<uint8_t>());
+ EXPECT_EQ(0u, rcursor.read<uint8_t>());
+ EXPECT_EQ(0u, rcursor.read<uint8_t>());
+ EXPECT_EQ(0u, rcursor.read<uint8_t>());
+ EXPECT_EQ(0u, rcursor.read<uint8_t>());
+ EXPECT_EQ(1u, rcursor.read<uint8_t>());
}
TEST(IOBuf, skip) {
std::string toString(const IOBuf& buf) {
std::string str;
Cursor cursor(&buf);
- std::pair<const uint8_t*, size_t> p;
- while ((p = cursor.peek()).second) {
- str.append(reinterpret_cast<const char*>(p.first), p.second);
- cursor.skip(p.second);
+ ByteRange b;
+ while (!(b = cursor.peekBytes()).empty()) {
+ str.append(reinterpret_cast<const char*>(b.data()), b.size());
+ cursor.skip(b.size());
}
return str;
}
{
RWPrivateCursor cursor(iobuf1.get());
- auto p = cursor.peek();
- EXPECT_EQ("he", std::string(reinterpret_cast<const char*>(p.first),
- p.second));
- cursor.skip(p.second);
- p = cursor.peek();
- EXPECT_EQ("llo ", std::string(reinterpret_cast<const char*>(p.first),
- p.second));
- cursor.skip(p.second);
- p = cursor.peek();
- EXPECT_EQ("world", std::string(reinterpret_cast<const char*>(p.first),
- p.second));
- cursor.skip(p.second);
+ auto b = cursor.peekBytes();
+ EXPECT_EQ("he", StringPiece(b));
+ cursor.skip(b.size());
+ b = cursor.peekBytes();
+ EXPECT_EQ("llo ", StringPiece(b));
+ cursor.skip(b.size());
+ b = cursor.peekBytes();
+ EXPECT_EQ("world", StringPiece(b));
+ cursor.skip(b.size());
EXPECT_EQ(3, iobuf1->countChainElements());
EXPECT_EQ(11, iobuf1->computeChainDataLength());
}
{
RWPrivateCursor cursor(iobuf1.get());
cursor.gather(11);
- auto p = cursor.peek();
- EXPECT_EQ("hello world", std::string(reinterpret_cast<const
- char*>(p.first), p.second));
+ auto b = cursor.peekBytes();
+ EXPECT_EQ("hello world", StringPiece(b));
EXPECT_EQ(1, iobuf1->countChainElements());
EXPECT_EQ(11, iobuf1->computeChainDataLength());
}
EXPECT_EQ(7, iobuf1->countChainElements());
EXPECT_EQ(14, iobuf1->computeChainDataLength());
// Check that nextBuf got set correctly to the buffer with 1 byte left
- EXPECT_EQ(1, cursor.peek().second);
+ EXPECT_EQ(1, cursor.peekBytes().size());
cursor.read<uint8_t>();
}
append(head, "hello");
Appender app(head.get(), 10);
- uint32_t cap = head->capacity();
- uint32_t len1 = app.length();
+ auto cap = head->capacity();
+ auto len1 = app.length();
EXPECT_EQ(cap - 5, len1);
app.ensure(len1); // won't grow
EXPECT_EQ(len1, app.length());
"longer than our original allocation size,",
"and will therefore require a new allocation", 0x12345678);
// The tailroom should start with a nul byte now.
- EXPECT_GE(head.prev()->tailroom(), 1);
+ EXPECT_GE(head.prev()->tailroom(), 1u);
EXPECT_EQ(0, *head.prev()->tail());
EXPECT_EQ("test32this string is longer than our original "
}
// There must be a goodMallocSize between 100 and 1024...
- EXPECT_LT(1, queue.front()->countChainElements());
+ EXPECT_LT(1u, queue.front()->countChainElements());
const IOBuf* buf = queue.front();
do {
- EXPECT_LE(100, buf->capacity());
+ EXPECT_LE(100u, buf->capacity());
buf = buf->next();
} while (buf != queue.front());
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(), 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_STREQ("hello", curs.readFixedString(5).c_str());
}
}
+
+TEST(IOBuf, ReadWhileTrue) {
+ auto isAlpha = [](uint8_t ch) {
+ return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
+ };
+ auto isDigit = [](uint8_t ch) { return (ch >= '0' && ch <= '9'); };
+
+ // Test reading alternating alphabetic and numeric strings
+ {
+ std::unique_ptr<IOBuf> chain(IOBuf::create(32));
+ Appender app(chain.get(), 0);
+ app.push(StringPiece("hello123world456"));
+
+ Cursor curs(chain.get());
+ EXPECT_STREQ("hello", curs.readWhile(isAlpha).c_str());
+ EXPECT_STREQ("123", curs.readWhile(isDigit).c_str());
+ EXPECT_STREQ("world", curs.readWhile(isAlpha).c_str());
+ EXPECT_STREQ("456", curs.readWhile(isDigit).c_str());
+ EXPECT_TRUE(curs.isAtEnd());
+ }
+
+ // The same, but also use skipWhile()
+ {
+ std::unique_ptr<IOBuf> chain(IOBuf::create(16));
+ Appender app(chain.get(), 0);
+ app.push(StringPiece("hello123world456"));
+
+ Cursor curs(chain.get());
+ EXPECT_STREQ("hello", curs.readWhile(isAlpha).c_str());
+ curs.skipWhile(isDigit);
+ curs.skipWhile(isAlpha);
+ EXPECT_STREQ("456", curs.readWhile(isDigit).c_str());
+ EXPECT_TRUE(curs.isAtEnd());
+ }
+
+ // Test readWhile() using data split across multiple buffers,
+ // including some empty buffers in the middle of the chain.
+ {
+ std::unique_ptr<IOBuf> chain;
+
+ // First element in the chain has "he"
+ auto buf = IOBuf::create(40);
+ Appender app(buf.get(), 0);
+ app.push(StringPiece("he"));
+ chain = std::move(buf);
+
+ // The second element has "ll", after 10 bytes of headroom
+ buf = IOBuf::create(40);
+ buf->advance(10);
+ app = Appender{buf.get(), 0};
+ app.push(StringPiece("ll"));
+ chain->prependChain(std::move(buf));
+
+ // The third element is empty
+ buf = IOBuf::create(40);
+ buf->advance(15);
+ chain->prependChain(std::move(buf));
+
+ // The fourth element has "o12"
+ buf = IOBuf::create(40);
+ buf->advance(37);
+ app = Appender{buf.get(), 0};
+ app.push(StringPiece("o12"));
+ chain->prependChain(std::move(buf));
+
+ // The fifth element has "3"
+ buf = IOBuf::create(40);
+ app = Appender{buf.get(), 0};
+ app.push(StringPiece("3"));
+ chain->prependChain(std::move(buf));
+
+ // The sixth element is empty
+ buf = IOBuf::create(40);
+ chain->prependChain(std::move(buf));
+
+ // The seventh element has "world456"
+ buf = IOBuf::create(40);
+ app = Appender{buf.get(), 0};
+ app.push(StringPiece("world456"));
+ chain->prependChain(std::move(buf));
+
+ // The eighth element is empty
+ buf = IOBuf::create(40);
+ chain->prependChain(std::move(buf));
+
+ Cursor curs(chain.get());
+ EXPECT_STREQ("hello", curs.readWhile(isAlpha).c_str());
+ EXPECT_STREQ("123", curs.readWhile(isDigit).c_str());
+ EXPECT_STREQ("world", curs.readWhile(isAlpha).c_str());
+ EXPECT_STREQ("456", curs.readWhile(isDigit).c_str());
+ EXPECT_TRUE(curs.isAtEnd());
+ }
+}
+
+TEST(IOBuf, TestAdvanceToEndSingle) {
+ std::unique_ptr<IOBuf> chain(IOBuf::create(10));
+ chain->append(10);
+
+ Cursor curs(chain.get());
+ curs.advanceToEnd();
+ EXPECT_TRUE(curs.isAtEnd());
+ EXPECT_EQ(curs - chain.get(), 10);
+}
+
+TEST(IOBuf, TestAdvanceToEndMulti) {
+ std::unique_ptr<IOBuf> chain(IOBuf::create(10));
+ chain->append(10);
+
+ std::unique_ptr<IOBuf> buf(IOBuf::create(5));
+ buf->append(5);
+ chain->prependChain(std::move(buf));
+
+ buf = IOBuf::create(20);
+ buf->append(20);
+ chain->prependChain(std::move(buf));
+
+ Cursor curs(chain.get());
+ curs.advanceToEnd();
+ EXPECT_TRUE(curs.isAtEnd());
+ EXPECT_EQ(curs - chain.get(), 35);
+
+ curs.reset(chain.get());
+ curs.skip(12);
+ curs.advanceToEnd();
+ EXPECT_TRUE(curs.isAtEnd());
+}
+
+TEST(IOBuf, TestRetreatSingle) {
+ std::unique_ptr<IOBuf> chain(IOBuf::create(20));
+ chain->append(20);
+
+ Cursor curs(chain.get());
+ EXPECT_EQ(curs.retreatAtMost(0), 0);
+ EXPECT_EQ(curs.totalLength(), 20);
+ EXPECT_EQ(curs.retreatAtMost(5), 0);
+ EXPECT_EQ(curs.totalLength(), 20);
+ EXPECT_EQ(curs.retreatAtMost(25), 0);
+ EXPECT_EQ(curs.totalLength(), 20);
+
+ curs.retreat(0);
+ EXPECT_THROW(curs.retreat(5), std::out_of_range);
+ curs.reset(chain.get());
+ EXPECT_THROW(curs.retreat(25), std::out_of_range);
+ curs.reset(chain.get());
+
+ curs.advanceToEnd();
+ curs.retreat(5);
+ EXPECT_EQ(curs.totalLength(), 5);
+ curs.retreat(10);
+ EXPECT_EQ(curs.totalLength(), 15);
+ EXPECT_THROW(curs.retreat(10), std::out_of_range);
+
+ curs.reset(chain.get());
+ curs.advanceToEnd();
+ EXPECT_EQ(curs.retreatAtMost(5), 5);
+ EXPECT_EQ(curs.totalLength(), 5);
+ EXPECT_EQ(curs.retreatAtMost(10), 10);
+ EXPECT_EQ(curs.totalLength(), 15);
+ EXPECT_EQ(curs.retreatAtMost(10), 5);
+ EXPECT_EQ(curs.totalLength(), 20);
+}
+
+TEST(IOBuf, TestRetreatMulti) {
+ std::unique_ptr<IOBuf> chain(IOBuf::create(10));
+ chain->append(10);
+
+ std::unique_ptr<IOBuf> buf(IOBuf::create(5));
+ buf->append(5);
+ chain->prependChain(std::move(buf));
+
+ buf = IOBuf::create(20);
+ buf->append(20);
+ chain->prependChain(std::move(buf));
+
+ Cursor curs(chain.get());
+ EXPECT_EQ(curs.retreatAtMost(10), 0);
+ EXPECT_THROW(curs.retreat(10), std::out_of_range);
+ curs.reset(chain.get());
+
+ curs.advanceToEnd();
+ curs.retreat(20);
+ EXPECT_EQ(curs.totalLength(), 20);
+ EXPECT_EQ(curs.length(), 20);
+ curs.retreat(1);
+ EXPECT_EQ(curs.totalLength(), 21);
+ EXPECT_EQ(curs.length(), 1);
+ EXPECT_EQ(curs.retreatAtMost(50), 14);
+ EXPECT_EQ(curs.totalLength(), 35);
+
+ curs.advanceToEnd();
+ curs.retreat(30);
+ EXPECT_EQ(curs.totalLength(), 30);
+}
+
+TEST(IOBuf, TestRetreatOperators) {
+ std::unique_ptr<IOBuf> chain(IOBuf::create(20));
+ chain->append(20);
+
+ Cursor curs(chain.get());
+ curs.advanceToEnd();
+ curs -= 5;
+ EXPECT_EQ(curs.totalLength(), 5);
+
+ curs.advanceToEnd();
+ auto retreated = curs - 5;
+ EXPECT_EQ(retreated.totalLength(), 5);
+ EXPECT_EQ(curs.totalLength(), 0);
+}