mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-11-22 16:02:34 +03:00
Make a ChainedByteRange (build fixes included)
Reviewed By: mjoras Differential Revision: D59473502 fbshipit-source-id: 30bb72fb5e07d12d9574a39fbeb9b8d3e76b36e6
This commit is contained in:
committed by
Facebook GitHub Bot
parent
916fe09268
commit
987475eb44
@@ -65,11 +65,14 @@ mvfst_cpp_library(
|
|||||||
name = "buf_util",
|
name = "buf_util",
|
||||||
srcs = [
|
srcs = [
|
||||||
"BufUtil.cpp",
|
"BufUtil.cpp",
|
||||||
|
"ChainedByteRange.cpp",
|
||||||
],
|
],
|
||||||
headers = [
|
headers = [
|
||||||
"BufUtil.h",
|
"BufUtil.h",
|
||||||
|
"ChainedByteRange.h",
|
||||||
],
|
],
|
||||||
exported_deps = [
|
exported_deps = [
|
||||||
|
"//folly:range",
|
||||||
"//folly/io:iobuf",
|
"//folly/io:iobuf",
|
||||||
"//quic:constants",
|
"//quic:constants",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -170,6 +170,14 @@ void BufWriter::insert(const folly::IOBuf* data, size_t limit) {
|
|||||||
copy(data, limit);
|
copy(data, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BufWriter::insert(const ChainedByteRangeHead* data) {
|
||||||
|
insert(data, data->chainLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufWriter::insert(const ChainedByteRangeHead* data, size_t limit) {
|
||||||
|
copy(&data->head, limit);
|
||||||
|
}
|
||||||
|
|
||||||
void BufWriter::append(size_t len) {
|
void BufWriter::append(size_t len) {
|
||||||
iobuf_.append(len);
|
iobuf_.append(len);
|
||||||
written_ += len;
|
written_ += len;
|
||||||
@@ -197,6 +205,27 @@ void BufWriter::copy(const folly::IOBuf* data, size_t limit) {
|
|||||||
CHECK_GE(limit, totalInserted);
|
CHECK_GE(limit, totalInserted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BufWriter::copy(const ChainedByteRange* data, size_t limit) {
|
||||||
|
if (!limit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sizeCheck(limit);
|
||||||
|
size_t totalInserted = 0;
|
||||||
|
const ChainedByteRange* curBuf = data;
|
||||||
|
auto remaining = limit;
|
||||||
|
do {
|
||||||
|
auto lenToCopy = std::min(curBuf->length(), remaining);
|
||||||
|
push(curBuf->getRange().begin(), lenToCopy);
|
||||||
|
totalInserted += lenToCopy;
|
||||||
|
remaining -= lenToCopy;
|
||||||
|
if (lenToCopy < curBuf->length()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
curBuf = curBuf->getNext();
|
||||||
|
} while (remaining && curBuf != data);
|
||||||
|
CHECK_GE(limit, totalInserted);
|
||||||
|
}
|
||||||
|
|
||||||
void BufWriter::backFill(const uint8_t* data, size_t len, size_t destOffset) {
|
void BufWriter::backFill(const uint8_t* data, size_t len, size_t destOffset) {
|
||||||
CHECK_GE(appendCount_, len);
|
CHECK_GE(appendCount_, len);
|
||||||
appendCount_ -= len;
|
appendCount_ -= len;
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <folly/Range.h>
|
||||||
#include <folly/io/IOBuf.h>
|
#include <folly/io/IOBuf.h>
|
||||||
#include <quic/QuicConstants.h>
|
#include <quic/QuicConstants.h>
|
||||||
|
#include <quic/common/ChainedByteRange.h>
|
||||||
|
|
||||||
namespace quic {
|
namespace quic {
|
||||||
|
|
||||||
@@ -121,6 +123,9 @@ class BufWriter {
|
|||||||
void insert(const folly::IOBuf* data);
|
void insert(const folly::IOBuf* data);
|
||||||
void insert(const folly::IOBuf* data, size_t limit);
|
void insert(const folly::IOBuf* data, size_t limit);
|
||||||
|
|
||||||
|
void insert(const ChainedByteRangeHead* data);
|
||||||
|
void insert(const ChainedByteRangeHead* data, size_t limit);
|
||||||
|
|
||||||
void append(size_t len);
|
void append(size_t len);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -135,6 +140,7 @@ class BufWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void copy(const folly::IOBuf* data, size_t limit);
|
void copy(const folly::IOBuf* data, size_t limit);
|
||||||
|
void copy(const ChainedByteRange* data, size_t limit);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
folly::IOBuf& iobuf_;
|
folly::IOBuf& iobuf_;
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ target_link_libraries(
|
|||||||
add_library(
|
add_library(
|
||||||
mvfst_bufutil
|
mvfst_bufutil
|
||||||
BufUtil.cpp
|
BufUtil.cpp
|
||||||
|
ChainedByteRange.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET mvfst_bufutil PROPERTY VERSION ${PACKAGE_VERSION})
|
set_property(TARGET mvfst_bufutil PROPERTY VERSION ${PACKAGE_VERSION})
|
||||||
|
|||||||
259
quic/common/ChainedByteRange.cpp
Normal file
259
quic/common/ChainedByteRange.cpp
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <quic/common/ChainedByteRange.h>
|
||||||
|
|
||||||
|
namespace quic {
|
||||||
|
|
||||||
|
[[nodiscard]] bool ChainedByteRange::empty() const {
|
||||||
|
if (range_.size() != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto* current = next_; current != this; current = current->next_) {
|
||||||
|
if (current->range_.size() != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::string ChainedByteRange::toStr() const {
|
||||||
|
std::string result;
|
||||||
|
result.reserve(computeChainDataLength());
|
||||||
|
result.append(range_.toString());
|
||||||
|
for (auto* current = next_; current != this; current = current->next_) {
|
||||||
|
result.append(current->range_.toString());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] size_t ChainedByteRange::computeChainDataLength() const {
|
||||||
|
size_t fullLength = range_.size();
|
||||||
|
for (auto* current = next_; current != this; current = current->next_) {
|
||||||
|
fullLength += current->range_.size();
|
||||||
|
}
|
||||||
|
return fullLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChainedByteRangeHead::ChainedByteRangeHead(const Buf& buf) {
|
||||||
|
if (!buf || buf->empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = buf->begin();
|
||||||
|
while (it != buf->end() && it->empty()) {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(it != buf->end());
|
||||||
|
head.range_ = *it++;
|
||||||
|
chainLength_ += head.range_.size();
|
||||||
|
|
||||||
|
ChainedByteRange* cur = &head;
|
||||||
|
for (; it != buf->end(); it++) {
|
||||||
|
chainLength_ += it->size();
|
||||||
|
auto next = std::make_unique<ChainedByteRange>().release();
|
||||||
|
next->range_ = *it;
|
||||||
|
next->prev_ = cur;
|
||||||
|
cur->next_ = next;
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
cur->next_ = &head;
|
||||||
|
head.prev_ = cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChainedByteRangeHead::append(const Buf& buf) {
|
||||||
|
if (!buf || buf->empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = buf->begin();
|
||||||
|
while (it != buf->end() && it->empty()) {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(it != buf->end());
|
||||||
|
// We know that *it is non-empty at this point because of the initial
|
||||||
|
// check that the chain is non-empty.
|
||||||
|
if (head.range_.empty()) {
|
||||||
|
head.range_ = *it;
|
||||||
|
chainLength_ += it->size();
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChainedByteRange* tail = head.prev_;
|
||||||
|
while (it != buf->end()) {
|
||||||
|
if (it->empty()) {
|
||||||
|
it++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* newElement = std::make_unique<ChainedByteRange>(*it).release();
|
||||||
|
chainLength_ += it->size();
|
||||||
|
newElement->next_ = &head;
|
||||||
|
newElement->prev_ = tail;
|
||||||
|
|
||||||
|
tail->next_ = newElement;
|
||||||
|
tail = newElement;
|
||||||
|
head.prev_ = newElement;
|
||||||
|
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChainedByteRangeHead::append(ChainedByteRangeHead&& chainHead) {
|
||||||
|
ChainedByteRange* oldTail = head.prev_;
|
||||||
|
// Since we're merging the input chain into this one, we need to create a
|
||||||
|
// ChainedByteRange for the data that's held as the first buffer in the input
|
||||||
|
// chain.
|
||||||
|
ChainedByteRange* headSubstitute =
|
||||||
|
std::make_unique<ChainedByteRange>(chainHead.head.getRange()).release();
|
||||||
|
ChainedByteRange* newTail = (chainHead.head.prev_ == &chainHead.head)
|
||||||
|
? headSubstitute
|
||||||
|
: chainHead.head.prev_;
|
||||||
|
|
||||||
|
headSubstitute->next_ =
|
||||||
|
(newTail == &chainHead.head) ? headSubstitute : chainHead.head.next_;
|
||||||
|
chainHead.head.next_->prev_ = headSubstitute;
|
||||||
|
headSubstitute->prev_ = oldTail;
|
||||||
|
oldTail->next_ = headSubstitute;
|
||||||
|
newTail->next_ = &head;
|
||||||
|
head.prev_ = newTail;
|
||||||
|
|
||||||
|
chainLength_ += chainHead.chainLength_;
|
||||||
|
|
||||||
|
chainHead.head.next_ = chainHead.head.prev_ = &chainHead.head;
|
||||||
|
chainHead.chainLength_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChainedByteRangeHead ChainedByteRangeHead::splitAtMost(size_t len) {
|
||||||
|
// entire chain requested
|
||||||
|
if (len >= chainLength_) {
|
||||||
|
return std::move(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChainedByteRangeHead ret;
|
||||||
|
ret.chainLength_ = len;
|
||||||
|
if (len == 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
chainLength_ -= len;
|
||||||
|
|
||||||
|
if (head.length() > len) {
|
||||||
|
// Just need to trim a little off the head.
|
||||||
|
ret.head.range_ =
|
||||||
|
folly::ByteRange(head.range_.begin(), head.range_.begin() + len);
|
||||||
|
ret.head.next_ = &ret.head;
|
||||||
|
ret.head.prev_ = &ret.head;
|
||||||
|
head.trimStart(len);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChainedByteRange* current = &head;
|
||||||
|
/**
|
||||||
|
* Find the last ChainedByteRange containing range requested. This will
|
||||||
|
* definitively terminate without looping back to head since we know length >
|
||||||
|
* len.
|
||||||
|
*/
|
||||||
|
while (len != 0) {
|
||||||
|
if (current->length() > len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
len -= current->length();
|
||||||
|
current = current->next_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
/**
|
||||||
|
* In this case, we're splitting at the boundary of two ChainedByteRanges.
|
||||||
|
* We make head take up the place of the first ChainedByteRange in the
|
||||||
|
* second chain.
|
||||||
|
*/
|
||||||
|
ChainedByteRange* tailOfSecondPart =
|
||||||
|
(head.prev_ == current) ? &head : head.prev_;
|
||||||
|
ChainedByteRange* tailOfFirstPart =
|
||||||
|
(current->prev_ == &head ? &ret.head : current->prev_);
|
||||||
|
|
||||||
|
ret.head.range_ = head.range_;
|
||||||
|
ret.head.next_ = head.next_;
|
||||||
|
ret.head.prev_ = tailOfFirstPart;
|
||||||
|
ret.head.next_->prev_ = &ret.head;
|
||||||
|
tailOfFirstPart->next_ = &ret.head;
|
||||||
|
|
||||||
|
head.range_ = current->range_;
|
||||||
|
head.next_ = current->next_;
|
||||||
|
head.prev_ = tailOfSecondPart;
|
||||||
|
head.next_->prev_ = &head;
|
||||||
|
tailOfSecondPart->next_ = &head;
|
||||||
|
|
||||||
|
delete current;
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* In this case, we're splitting somewhere in the middle of a
|
||||||
|
* ChainedByteRange.
|
||||||
|
*/
|
||||||
|
ChainedByteRange* tailOfFirstPart = current;
|
||||||
|
ChainedByteRange* tailOfSecondPart =
|
||||||
|
(head.prev_ == tailOfFirstPart) ? &head : head.prev_;
|
||||||
|
|
||||||
|
ret.head.range_ = head.range_;
|
||||||
|
|
||||||
|
head.range_ =
|
||||||
|
folly::ByteRange(current->range_.begin() + len, current->range_.end());
|
||||||
|
current->range_ = folly::ByteRange(
|
||||||
|
current->range_.begin(), current->range_.begin() + len);
|
||||||
|
|
||||||
|
ret.head.next_ = head.next_;
|
||||||
|
ret.head.prev_ = tailOfFirstPart;
|
||||||
|
ret.head.next_->prev_ = &ret.head;
|
||||||
|
|
||||||
|
head.next_ = tailOfFirstPart->next_;
|
||||||
|
tailOfFirstPart->next_->prev_ = &head;
|
||||||
|
head.prev_ = tailOfSecondPart;
|
||||||
|
|
||||||
|
tailOfFirstPart->next_ = &ret.head;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ChainedByteRangeHead::trimStartAtMost(size_t len) {
|
||||||
|
size_t amountToSplit = std::min(len, chainLength());
|
||||||
|
auto splitRch = splitAtMost(amountToSplit);
|
||||||
|
return amountToSplit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChainedByteRangeHead::resetChain() {
|
||||||
|
ChainedByteRange* curr = head.next_;
|
||||||
|
while (curr != &head) {
|
||||||
|
auto* next = curr->next_;
|
||||||
|
delete curr;
|
||||||
|
curr = next;
|
||||||
|
}
|
||||||
|
head.next_ = &head;
|
||||||
|
head.prev_ = &head;
|
||||||
|
chainLength_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChainedByteRangeHead::moveChain(ChainedByteRangeHead&& other) {
|
||||||
|
head.range_ = other.head.range_;
|
||||||
|
ChainedByteRange* headNext = other.head.next_;
|
||||||
|
ChainedByteRange* headPrev = other.head.prev_;
|
||||||
|
headNext->prev_ = &head;
|
||||||
|
headPrev->next_ = &head;
|
||||||
|
head.next_ = other.head.next_;
|
||||||
|
head.prev_ = other.head.prev_;
|
||||||
|
|
||||||
|
other.head.range_ = folly::ByteRange();
|
||||||
|
other.head.next_ = &other.head;
|
||||||
|
other.head.prev_ = &other.head;
|
||||||
|
chainLength_ = other.chainLength_;
|
||||||
|
other.chainLength_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace quic
|
||||||
127
quic/common/ChainedByteRange.h
Normal file
127
quic/common/ChainedByteRange.h
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <folly/Range.h>
|
||||||
|
#include <quic/QuicConstants.h>
|
||||||
|
|
||||||
|
namespace quic {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The ChainedByteRange depicts one block of contiguous
|
||||||
|
* memory, and has a next_ and a prev_ pointer. It has APIs
|
||||||
|
* that can be used to trim the start or end of this specific
|
||||||
|
* contiguous memory block.
|
||||||
|
*/
|
||||||
|
class ChainedByteRange {
|
||||||
|
public:
|
||||||
|
ChainedByteRange() : next_(this), prev_(this) {}
|
||||||
|
|
||||||
|
explicit ChainedByteRange(folly::ByteRange range)
|
||||||
|
: range_(range), next_(this), prev_(this) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length only of this ChainedByteRange
|
||||||
|
*/
|
||||||
|
[[nodiscard]] size_t length() const {
|
||||||
|
return range_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the entire chain is empty
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool empty() const;
|
||||||
|
|
||||||
|
[[nodiscard]] std::string toStr() const;
|
||||||
|
|
||||||
|
[[nodiscard]] size_t computeChainDataLength() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim the start of this specific contiguous memory block
|
||||||
|
*/
|
||||||
|
void trimStart(size_t n) {
|
||||||
|
n = std::min(n, range_.size());
|
||||||
|
range_.advance(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] folly::ByteRange getRange() const {
|
||||||
|
return range_;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ChainedByteRange* getNext() const {
|
||||||
|
return next_;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ChainedByteRange* getPrev() const {
|
||||||
|
return prev_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
folly::ByteRange range_;
|
||||||
|
ChainedByteRange* next_{nullptr};
|
||||||
|
ChainedByteRange* prev_{nullptr};
|
||||||
|
|
||||||
|
friend class ChainedByteRangeHead;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The ChainedByteRangeHead depicts the head of a chain of ChainedByteRanges.
|
||||||
|
* It caches the length of the total chain, which is useful in many cases
|
||||||
|
* because we don't want to walk the entire chain to get the length.
|
||||||
|
* Additionally, it allows us to trim or split off multiple ChainedByteRanges
|
||||||
|
* with the splitAtMost and trimStartAtMost APIs.
|
||||||
|
*/
|
||||||
|
class ChainedByteRangeHead {
|
||||||
|
public:
|
||||||
|
ChainedByteRange head;
|
||||||
|
|
||||||
|
explicit ChainedByteRangeHead(const Buf& buf);
|
||||||
|
|
||||||
|
ChainedByteRangeHead() = default;
|
||||||
|
|
||||||
|
ChainedByteRangeHead(ChainedByteRangeHead&& other) noexcept {
|
||||||
|
moveChain(std::move(other));
|
||||||
|
}
|
||||||
|
|
||||||
|
ChainedByteRangeHead& operator=(ChainedByteRangeHead&& other) noexcept {
|
||||||
|
resetChain();
|
||||||
|
moveChain(std::move(other));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~ChainedByteRangeHead() {
|
||||||
|
resetChain();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const {
|
||||||
|
return chainLength_ == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] size_t chainLength() const {
|
||||||
|
return chainLength_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits off the initial n bytes from the chain and returns them.
|
||||||
|
*/
|
||||||
|
ChainedByteRangeHead splitAtMost(size_t n);
|
||||||
|
|
||||||
|
size_t trimStartAtMost(size_t len);
|
||||||
|
|
||||||
|
void append(const Buf& buf);
|
||||||
|
|
||||||
|
void append(ChainedByteRangeHead&& chainHead);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void resetChain();
|
||||||
|
|
||||||
|
void moveChain(ChainedByteRangeHead&& other);
|
||||||
|
|
||||||
|
size_t chainLength_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace quic
|
||||||
@@ -59,6 +59,18 @@ cpp_unittest(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cpp_unittest(
|
||||||
|
name = "ChainedByteRangeTest",
|
||||||
|
srcs = [
|
||||||
|
"ChainedByteRangeTest.cpp",
|
||||||
|
],
|
||||||
|
deps = [
|
||||||
|
"//folly:string",
|
||||||
|
"//folly/io:iobuf",
|
||||||
|
"//quic/common:buf_util",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
mvfst_cpp_library(
|
mvfst_cpp_library(
|
||||||
name = "test_client_utils",
|
name = "test_client_utils",
|
||||||
headers = [
|
headers = [
|
||||||
|
|||||||
@@ -561,3 +561,61 @@ TEST(BufWriterTest, TwoWriters) {
|
|||||||
EXPECT_EQ(15, outputBuffer->length());
|
EXPECT_EQ(15, outputBuffer->length());
|
||||||
EXPECT_EQ("Destroyer Saint", reader.readFixedString(outputBuffer->length()));
|
EXPECT_EQ("Destroyer Saint", reader.readFixedString(outputBuffer->length()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(BufWriterTest, InsertSingleByteChainedRange) {
|
||||||
|
auto testBuffer = folly::IOBuf::create(100);
|
||||||
|
BufWriter writer(*testBuffer, 100);
|
||||||
|
auto inputBuffer =
|
||||||
|
folly::IOBuf::copyBuffer("Steady on dreaming, I sleepwalk");
|
||||||
|
auto len = inputBuffer->computeChainDataLength();
|
||||||
|
ChainedByteRangeHead cbrh(inputBuffer);
|
||||||
|
writer.insert(&cbrh);
|
||||||
|
folly::io::Cursor reader(testBuffer.get());
|
||||||
|
EXPECT_EQ(
|
||||||
|
inputBuffer->computeChainDataLength(),
|
||||||
|
testBuffer->computeChainDataLength());
|
||||||
|
EXPECT_EQ(inputBuffer->to<string>(), reader.readFixedString(len));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BufWriterTest, InsertZeroLen) {
|
||||||
|
auto testBuffer = folly::IOBuf::create(100);
|
||||||
|
BufWriter writer(*testBuffer, 100);
|
||||||
|
auto inputBuffer = folly::IOBuf::copyBuffer("");
|
||||||
|
ChainedByteRangeHead cbrh(inputBuffer);
|
||||||
|
writer.insert(&cbrh);
|
||||||
|
folly::io::Cursor reader(testBuffer.get());
|
||||||
|
EXPECT_EQ(testBuffer->computeChainDataLength(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BufWriterTest, InsertSingleByteChainedRangeWithLimit) {
|
||||||
|
auto testBuffer = folly::IOBuf::create(100);
|
||||||
|
BufWriter writer(*testBuffer, 100);
|
||||||
|
auto inputBuffer =
|
||||||
|
folly::IOBuf::copyBuffer("Steady on dreaming, I sleepwalk");
|
||||||
|
ChainedByteRangeHead cbrh(inputBuffer);
|
||||||
|
writer.insert(&cbrh, 10);
|
||||||
|
folly::io::Cursor reader(testBuffer.get());
|
||||||
|
EXPECT_EQ(testBuffer->computeChainDataLength(), 10);
|
||||||
|
EXPECT_EQ("Steady on ", reader.readFixedString(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(BufWriterTest, InsertChainByteChainedRange) {
|
||||||
|
auto testBuffer = folly::IOBuf::create(1000);
|
||||||
|
BufWriter writer(*testBuffer, 1000);
|
||||||
|
auto inputBuffer =
|
||||||
|
folly::IOBuf::copyBuffer("Cause I lost you and now what am i to do?");
|
||||||
|
inputBuffer->prependChain(
|
||||||
|
folly::IOBuf::copyBuffer(" Can't believe that we are through."));
|
||||||
|
inputBuffer->prependChain(
|
||||||
|
folly::IOBuf::copyBuffer(" While the memory of you linger like a song."));
|
||||||
|
auto len = inputBuffer->computeChainDataLength();
|
||||||
|
ChainedByteRangeHead cbrh(inputBuffer);
|
||||||
|
writer.insert(&cbrh);
|
||||||
|
folly::io::Cursor reader(testBuffer.get());
|
||||||
|
EXPECT_EQ(testBuffer->computeChainDataLength(), len);
|
||||||
|
EXPECT_EQ(
|
||||||
|
"Cause I lost you and now what am i to do?"
|
||||||
|
" Can't believe that we are through."
|
||||||
|
" While the memory of you linger like a song.",
|
||||||
|
reader.readFixedString(len));
|
||||||
|
}
|
||||||
|
|||||||
480
quic/common/test/ChainedByteRangeTest.cpp
Normal file
480
quic/common/test/ChainedByteRangeTest.cpp
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <folly/String.h>
|
||||||
|
#include <folly/io/Cursor.h>
|
||||||
|
#include <quic/common/ChainedByteRange.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace folly;
|
||||||
|
using namespace quic;
|
||||||
|
|
||||||
|
#define SCL(x) (x), sizeof(x) - 1
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void checkConsistency(const ChainedByteRangeHead& queue) {
|
||||||
|
size_t len = queue.head.computeChainDataLength();
|
||||||
|
EXPECT_EQ(len, queue.chainLength());
|
||||||
|
|
||||||
|
ChainedByteRange* current = queue.head.getNext();
|
||||||
|
EXPECT_EQ(queue.head.getNext()->getPrev(), &queue.head);
|
||||||
|
EXPECT_EQ(queue.head.getPrev()->getNext(), &queue.head);
|
||||||
|
while (current != &queue.head) {
|
||||||
|
EXPECT_EQ(current->getNext()->getPrev(), current);
|
||||||
|
EXPECT_EQ(current->getPrev()->getNext(), current);
|
||||||
|
current = current->getNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
static auto kHello = IOBuf::copyBuffer(SCL("Hello"));
|
||||||
|
static auto kCommaSpace = IOBuf::copyBuffer(SCL(", "));
|
||||||
|
static auto kComma = IOBuf::copyBuffer(SCL(","));
|
||||||
|
static auto kSpace = IOBuf::copyBuffer(SCL(" "));
|
||||||
|
static auto kEmpty = IOBuf::copyBuffer(SCL(""));
|
||||||
|
static auto kWorld = IOBuf::copyBuffer(SCL("World"));
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, AppendBasic) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kSpace);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(queue.head.computeChainDataLength(), 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, Append) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
ChainedByteRangeHead queue2;
|
||||||
|
queue2.append(kCommaSpace);
|
||||||
|
queue2.append(kWorld);
|
||||||
|
checkConsistency(queue);
|
||||||
|
checkConsistency(queue2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, Append2) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
ChainedByteRangeHead queue2;
|
||||||
|
queue2.append(kCommaSpace);
|
||||||
|
queue2.append(kWorld);
|
||||||
|
checkConsistency(queue);
|
||||||
|
checkConsistency(queue2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, AppendHead) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
ChainedByteRangeHead queue2;
|
||||||
|
queue2.append(kCommaSpace);
|
||||||
|
queue.append(std::move(queue2));
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(queue.head.computeChainDataLength(), 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, AppendHead2) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kComma);
|
||||||
|
ChainedByteRangeHead queue2;
|
||||||
|
queue2.append(kSpace);
|
||||||
|
queue2.append(kWorld);
|
||||||
|
queue.append(std::move(queue2));
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(queue.head.computeChainDataLength(), 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, AppendHead3) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kComma);
|
||||||
|
ChainedByteRangeHead queue2;
|
||||||
|
queue2.append(kSpace);
|
||||||
|
queue.append(std::move(queue2));
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(queue.head.computeChainDataLength(), 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, AppendHead4) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
ChainedByteRangeHead queue2;
|
||||||
|
queue2.append(kComma);
|
||||||
|
queue2.append(kSpace);
|
||||||
|
queue.append(std::move(queue2));
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(queue.head.computeChainDataLength(), 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, AppendMultipleEmpty) {
|
||||||
|
auto buf = folly::IOBuf::copyBuffer("");
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer(""));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("apple"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("ball"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer(""));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("dog"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("cat"));
|
||||||
|
|
||||||
|
ChainedByteRangeHead chainedByteRangeHead;
|
||||||
|
chainedByteRangeHead.append(buf);
|
||||||
|
EXPECT_EQ(chainedByteRangeHead.chainLength(), 15);
|
||||||
|
EXPECT_EQ(chainedByteRangeHead.head.toStr(), "appleballdogcat");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, AppendStringPiece) {
|
||||||
|
std::string s("Hello, World");
|
||||||
|
auto helloWorld = IOBuf::copyBuffer(s);
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
ChainedByteRangeHead queue2;
|
||||||
|
queue.append(helloWorld);
|
||||||
|
queue2.append(helloWorld);
|
||||||
|
checkConsistency(queue);
|
||||||
|
checkConsistency(queue2);
|
||||||
|
EXPECT_EQ(s.length(), queue.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(s.length(), queue2.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(
|
||||||
|
0,
|
||||||
|
memcmp(
|
||||||
|
queue.head.getRange().data(),
|
||||||
|
queue2.head.getRange().data(),
|
||||||
|
s.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, Splitttt) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kComma);
|
||||||
|
queue.append(kSpace);
|
||||||
|
queue.append(kEmpty);
|
||||||
|
queue.append(kWorld);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(12, queue.head.computeChainDataLength());
|
||||||
|
|
||||||
|
auto prefix = queue.splitAtMost(1);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(1, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(11, queue.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "H");
|
||||||
|
ChainedByteRangeHead rch1(std::move(prefix));
|
||||||
|
|
||||||
|
prefix = queue.splitAtMost(2);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(2, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(9, queue.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "el");
|
||||||
|
ChainedByteRangeHead rch2(std::move(prefix));
|
||||||
|
|
||||||
|
prefix = queue.splitAtMost(3);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(3, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(6, queue.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "lo,");
|
||||||
|
ChainedByteRangeHead rch3(std::move(prefix));
|
||||||
|
|
||||||
|
prefix = queue.splitAtMost(1);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(1, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(5, queue.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), " ");
|
||||||
|
ChainedByteRangeHead rch4(std::move(prefix));
|
||||||
|
|
||||||
|
prefix = queue.splitAtMost(5);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(5, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_TRUE(queue.empty());
|
||||||
|
EXPECT_EQ(queue.chainLength(), 0);
|
||||||
|
EXPECT_TRUE(queue.head.getRange().empty());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "World");
|
||||||
|
ChainedByteRangeHead rch5(std::move(prefix));
|
||||||
|
|
||||||
|
auto helloComma = IOBuf::copyBuffer(SCL("Hello,"));
|
||||||
|
queue.append(helloComma);
|
||||||
|
checkConsistency(queue);
|
||||||
|
prefix = queue.splitAtMost(3);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(3, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(3, queue.chainLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "Hel");
|
||||||
|
ChainedByteRangeHead rch6(std::move(prefix));
|
||||||
|
|
||||||
|
auto spaceWorld = IOBuf::copyBuffer(SCL(" World"));
|
||||||
|
queue.append(spaceWorld);
|
||||||
|
checkConsistency(queue);
|
||||||
|
prefix = queue.splitAtMost(13);
|
||||||
|
EXPECT_EQ(9, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(0, queue.chainLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "lo, World");
|
||||||
|
checkConsistency(queue);
|
||||||
|
ChainedByteRangeHead rch7(std::move(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, Empty) {
|
||||||
|
ChainedByteRangeHead emptyQueue;
|
||||||
|
checkConsistency(emptyQueue);
|
||||||
|
EXPECT_TRUE(emptyQueue.empty());
|
||||||
|
EXPECT_EQ(emptyQueue.chainLength(), 0);
|
||||||
|
|
||||||
|
emptyQueue.append(folly::IOBuf::copyBuffer("apple"));
|
||||||
|
checkConsistency(emptyQueue);
|
||||||
|
EXPECT_FALSE(emptyQueue.head.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, FromIobuf) {
|
||||||
|
auto buf = folly::IOBuf::copyBuffer("");
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer(""));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("apple"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("ball"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer(""));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("dog"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("cat"));
|
||||||
|
|
||||||
|
ChainedByteRangeHead chainedByteRangeHead(buf);
|
||||||
|
EXPECT_EQ(chainedByteRangeHead.chainLength(), 15);
|
||||||
|
EXPECT_EQ(chainedByteRangeHead.head.toStr(), "appleballdogcat");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, FromIobufEmpty) {
|
||||||
|
auto buf = folly::IOBuf::copyBuffer("");
|
||||||
|
ChainedByteRangeHead chainedByteRangeHead(buf);
|
||||||
|
EXPECT_TRUE(chainedByteRangeHead.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, TrimStart) {
|
||||||
|
auto cbr = std::make_unique<ChainedByteRange>(
|
||||||
|
folly::ByteRange(kHello->data(), kHello->length()));
|
||||||
|
cbr->trimStart(3);
|
||||||
|
EXPECT_EQ(cbr->toStr(), "lo");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, SplitHeadFromChainOfOne) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(5, queue.head.computeChainDataLength());
|
||||||
|
|
||||||
|
auto prefix = queue.splitAtMost(3);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(3, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(2, queue.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "Hel");
|
||||||
|
EXPECT_EQ(queue.head.toStr(), "lo");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, MoveAssignmentOperator) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kWorld);
|
||||||
|
|
||||||
|
ChainedByteRangeHead queue2 = std::move(queue);
|
||||||
|
EXPECT_EQ(queue2.chainLength(), 10);
|
||||||
|
EXPECT_TRUE(queue.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, SplitHeadFromChainOfTwo) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kWorld);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(10, queue.head.computeChainDataLength());
|
||||||
|
|
||||||
|
auto prefix = queue.splitAtMost(3);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(3, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(7, queue.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "Hel");
|
||||||
|
EXPECT_EQ(queue.head.toStr(), "loWorld");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, SplitOneAndHalfFromChainOfTwo) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kWorld);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(10, queue.head.computeChainDataLength());
|
||||||
|
|
||||||
|
auto prefix = queue.splitAtMost(7);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(7, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(3, queue.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "HelloWo");
|
||||||
|
EXPECT_EQ(queue.head.toStr(), "rld");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, SplitOneAndHalfFromChainOfThree) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kWorld);
|
||||||
|
queue.append(kHello);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(15, queue.head.computeChainDataLength());
|
||||||
|
|
||||||
|
auto prefix = queue.splitAtMost(7);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(7, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(8, queue.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "HelloWo");
|
||||||
|
EXPECT_EQ(queue.head.toStr(), "rldHello");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, SplitOneAndHalfFromChainOfFour) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kWorld);
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kWorld);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(20, queue.head.computeChainDataLength());
|
||||||
|
|
||||||
|
auto prefix = queue.splitAtMost(7);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_EQ(7, prefix.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(13, queue.head.computeChainDataLength());
|
||||||
|
EXPECT_EQ(prefix.head.toStr(), "HelloWo");
|
||||||
|
EXPECT_EQ(queue.head.toStr(), "rldHelloWorld");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, SplitZero) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
auto helloWorld = IOBuf::copyBuffer(SCL("Hello world"));
|
||||||
|
queue.append(helloWorld);
|
||||||
|
auto splitRch = queue.splitAtMost(0);
|
||||||
|
EXPECT_EQ(splitRch.head.computeChainDataLength(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, SplitEmpty) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
auto splitRch = queue.splitAtMost(0);
|
||||||
|
EXPECT_EQ(splitRch.head.computeChainDataLength(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, SplitEmptt) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
auto splitRch = queue.splitAtMost(1);
|
||||||
|
EXPECT_EQ(splitRch.head.computeChainDataLength(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, TrimStartAtMost) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
queue.append(kHello);
|
||||||
|
auto prefixLen = queue.trimStartAtMost(3);
|
||||||
|
EXPECT_EQ(3, prefixLen);
|
||||||
|
EXPECT_EQ(2, queue.chainLength());
|
||||||
|
checkConsistency(queue);
|
||||||
|
|
||||||
|
prefixLen = queue.trimStartAtMost(2);
|
||||||
|
EXPECT_EQ(2, prefixLen);
|
||||||
|
EXPECT_EQ(0, queue.chainLength());
|
||||||
|
checkConsistency(queue);
|
||||||
|
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kWorld);
|
||||||
|
prefixLen = queue.trimStartAtMost(7);
|
||||||
|
EXPECT_EQ(7, prefixLen);
|
||||||
|
EXPECT_EQ(3, queue.chainLength());
|
||||||
|
checkConsistency(queue);
|
||||||
|
|
||||||
|
prefixLen = queue.trimStartAtMost(10);
|
||||||
|
EXPECT_EQ(3, prefixLen);
|
||||||
|
EXPECT_EQ(0, queue.chainLength());
|
||||||
|
checkConsistency(queue);
|
||||||
|
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kWorld);
|
||||||
|
|
||||||
|
prefixLen = queue.trimStartAtMost(12);
|
||||||
|
EXPECT_EQ(10, prefixLen);
|
||||||
|
EXPECT_EQ(0, queue.chainLength());
|
||||||
|
checkConsistency(queue);
|
||||||
|
|
||||||
|
queue.append(kHello);
|
||||||
|
queue.append(kWorld);
|
||||||
|
queue.append(kHello);
|
||||||
|
|
||||||
|
prefixLen = queue.trimStartAtMost(12);
|
||||||
|
EXPECT_EQ(12, prefixLen);
|
||||||
|
EXPECT_EQ(3, queue.chainLength());
|
||||||
|
checkConsistency(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, TrimStartOneByte) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
auto h = IOBuf::copyBuffer(SCL("H"));
|
||||||
|
queue.append(h);
|
||||||
|
checkConsistency(queue);
|
||||||
|
queue.trimStartAtMost(1);
|
||||||
|
checkConsistency(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, TrimStartClearChain) {
|
||||||
|
ChainedByteRangeHead queue;
|
||||||
|
constexpr string_view alphabet = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
auto buf = IOBuf::copyBuffer(alphabet);
|
||||||
|
queue.append(buf);
|
||||||
|
queue.append(buf);
|
||||||
|
// validate chain length
|
||||||
|
const size_t expectedChainLength = alphabet.size() * 2;
|
||||||
|
EXPECT_EQ(queue.chainLength(), expectedChainLength);
|
||||||
|
checkConsistency(queue);
|
||||||
|
// attempt to trim more than chainLength
|
||||||
|
queue.trimStartAtMost(expectedChainLength + 1);
|
||||||
|
checkConsistency(queue);
|
||||||
|
EXPECT_TRUE(queue.empty());
|
||||||
|
EXPECT_EQ(queue.chainLength(), 0);
|
||||||
|
EXPECT_TRUE(queue.head.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, TestEmptyWithMiddleEmptyBuffer) {
|
||||||
|
ChainedByteRangeHead queue1;
|
||||||
|
queue1.append(kHello);
|
||||||
|
ChainedByteRangeHead queue2;
|
||||||
|
queue1.append(std::move(queue2));
|
||||||
|
queue1.append(kWorld);
|
||||||
|
EXPECT_FALSE(queue1.head.getNext()->empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, TestMove) {
|
||||||
|
auto buf = folly::IOBuf::copyBuffer("corporate america");
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("apple"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("ball"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("dog"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("cat"));
|
||||||
|
|
||||||
|
ChainedByteRangeHead queue(std::move(buf));
|
||||||
|
checkConsistency(queue);
|
||||||
|
|
||||||
|
ChainedByteRangeHead queue2(std::move(queue));
|
||||||
|
checkConsistency(queue2);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, TestSplitAtMostRemoveFirstChunk) {
|
||||||
|
auto buf = folly::IOBuf::copyBuffer("jars");
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("apple"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("ball"));
|
||||||
|
|
||||||
|
ChainedByteRangeHead queue(buf);
|
||||||
|
checkConsistency(queue);
|
||||||
|
|
||||||
|
auto prefix = queue.splitAtMost(4);
|
||||||
|
EXPECT_EQ(prefix.chainLength(), 4);
|
||||||
|
EXPECT_EQ(queue.chainLength(), 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ChainedByteRangeHead, TestSplitAtMostRemoveAllExceptLast) {
|
||||||
|
auto buf = folly::IOBuf::copyBuffer("jars");
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("apple"));
|
||||||
|
buf->appendToChain(folly::IOBuf::copyBuffer("ball"));
|
||||||
|
|
||||||
|
ChainedByteRangeHead queue(buf);
|
||||||
|
checkConsistency(queue);
|
||||||
|
|
||||||
|
auto prefix = queue.splitAtMost(9);
|
||||||
|
EXPECT_EQ(prefix.chainLength(), 9);
|
||||||
|
EXPECT_EQ(queue.chainLength(), 4);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user