blob: 09dd17dab9c4b3d969b22ca6ef5141b916c05a4b [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <gtest/gtest.h>
#include <sys/types.h>
#include <sys/socket.h> // socketpair
#include <errno.h> // errno
#include <fcntl.h> // O_RDONLY
#include <stdlib.h>
#include <memory>
#include <butil/files/temp_file.h> // TempFile
#include <butil/containers/flat_map.h>
#include <butil/macros.h>
#include <butil/time.h> // Timer
#include <butil/fd_utility.h> // make_non_blocking
#include <butil/iobuf.h>
#include <butil/logging.h>
#include <butil/fd_guard.h>
#include <butil/errno.h>
#include <butil/fast_rand.h>
#if BAZEL_TEST
#include "test/iobuf.pb.h"
#else
#include "iobuf.pb.h"
#endif // BAZEL_TEST
namespace butil {
namespace iobuf {
extern void* (*blockmem_allocate)(size_t);
extern void (*blockmem_deallocate)(void*);
extern void reset_blockmem_allocate_and_deallocate();
extern int32_t block_shared_count(butil::IOBuf::Block const* b);
extern uint32_t block_cap(butil::IOBuf::Block const* b);
extern IOBuf::Block* get_tls_block_head();
extern int get_tls_block_count();
extern void remove_tls_block_chain();
extern IOBuf::Block* acquire_tls_block();
extern IOBuf::Block* share_tls_block();
extern void release_tls_block_chain(IOBuf::Block* b);
extern uint32_t block_cap(IOBuf::Block const* b);
extern uint32_t block_size(IOBuf::Block const* b);
extern IOBuf::Block* get_portal_next(IOBuf::Block const* b);
}
}
namespace {
const size_t BLOCK_OVERHEAD = 32; //impl dependent
const size_t DEFAULT_PAYLOAD = butil::IOBuf::DEFAULT_BLOCK_SIZE - BLOCK_OVERHEAD;
void check_tls_block() {
ASSERT_EQ((butil::IOBuf::Block*)NULL, butil::iobuf::get_tls_block_head());
printf("tls_block of butil::IOBuf was deleted\n");
}
const int ALLOW_UNUSED check_dummy = butil::thread_atexit(check_tls_block);
static butil::FlatSet<void*> s_set;
void* debug_block_allocate(size_t block_size) {
void* b = operator new (block_size, std::nothrow);
s_set.insert(b);
return b;
}
void debug_block_deallocate(void* b) {
if (1ul != s_set.erase(b)) {
ASSERT_TRUE(false) << "Bad block=" << b;
} else {
operator delete(b);
}
}
inline bool is_debug_allocator_enabled() {
return (butil::iobuf::blockmem_allocate == debug_block_allocate);
}
void install_debug_allocator() {
if (!is_debug_allocator_enabled()) {
butil::iobuf::remove_tls_block_chain();
s_set.init(1024);
butil::iobuf::blockmem_allocate = debug_block_allocate;
butil::iobuf::blockmem_deallocate = debug_block_deallocate;
LOG(INFO) << "<Installed debug create/destroy>";
}
}
void show_prof_and_rm(const char* bin_name, const char* filename, size_t topn) {
char cmd[1024];
if (topn != 0) {
snprintf(cmd, sizeof(cmd), "if [ -e %s ] ; then CPUPROFILE_FREQUENCY=1000 ./pprof --text %s %s | head -%lu; rm -f %s; fi", filename, bin_name, filename, topn+1, filename);
} else {
snprintf(cmd, sizeof(cmd), "if [ -e %s ] ; then CPUPROFILE_FREQUENCY=1000 ./pprof --text %s %s; rm -f %s; fi", filename, bin_name, filename, filename);
}
ASSERT_EQ(0, system(cmd));
}
static void check_memory_leak() {
if (is_debug_allocator_enabled()) {
butil::IOBuf::Block* p = butil::iobuf::get_tls_block_head();
size_t n = 0;
while (p) {
ASSERT_TRUE(s_set.seek(p)) << "Memory leak: " << p;
p = butil::iobuf::get_portal_next(p);
++n;
}
ASSERT_EQ(n, s_set.size());
ASSERT_EQ(n, (size_t)butil::iobuf::get_tls_block_count());
}
}
class IOBufTest : public ::testing::Test{
protected:
IOBufTest(){};
virtual ~IOBufTest(){};
virtual void SetUp() {
};
virtual void TearDown() {
check_memory_leak();
};
};
std::string to_str(const butil::IOBuf& p) {
return p.to_string();
}
TEST_F(IOBufTest, append_zero) {
int fds[2];
ASSERT_EQ(0, pipe(fds));
butil::IOPortal p;
ASSERT_EQ(0, p.append_from_file_descriptor(fds[0], 0));
ASSERT_EQ(0, close(fds[0]));
ASSERT_EQ(0, close(fds[1]));
}
TEST_F(IOBufTest, pop_front) {
install_debug_allocator();
butil::IOBuf buf;
ASSERT_EQ(0UL, buf.pop_front(1)); // nothing happened
std::string s = "hello";
buf.append(s);
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(0UL, buf.pop_front(0)); // nothing happened
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(1UL, buf.pop_front(1));
s.erase(0, 1);
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(s.length(), buf.length());
ASSERT_FALSE(buf.empty());
ASSERT_EQ(s.length(), buf.pop_front(INT_MAX));
s.clear();
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(0UL, buf.length());
ASSERT_TRUE(buf.empty());
for (size_t i = 0; i < DEFAULT_PAYLOAD * 3/2; ++i) {
s.push_back(i);
}
buf.append(s);
ASSERT_EQ(1UL, buf.pop_front(1));
s.erase(0, 1);
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(s.length(), buf.length());
ASSERT_FALSE(buf.empty());
ASSERT_EQ(s.length(), buf.pop_front(INT_MAX));
s.clear();
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(0UL, buf.length());
ASSERT_TRUE(buf.empty());
}
TEST_F(IOBufTest, pop_back) {
install_debug_allocator();
butil::IOBuf buf;
ASSERT_EQ(0UL, buf.pop_back(1)); // nothing happened
std::string s = "hello";
buf.append(s);
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(0UL, buf.pop_back(0)); // nothing happened
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(1UL, buf.pop_back(1));
s.resize(s.size() - 1);
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(s.length(), buf.length());
ASSERT_FALSE(buf.empty());
ASSERT_EQ(s.length(), buf.pop_back(INT_MAX));
s.clear();
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(0UL, buf.length());
ASSERT_TRUE(buf.empty());
for (size_t i = 0; i < DEFAULT_PAYLOAD * 3/2; ++i) {
s.push_back(i);
}
buf.append(s);
ASSERT_EQ(1UL, buf.pop_back(1));
s.resize(s.size() - 1);
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(s.length(), buf.length());
ASSERT_FALSE(buf.empty());
ASSERT_EQ(s.length(), buf.pop_back(INT_MAX));
s.clear();
ASSERT_EQ(s, to_str(buf));
ASSERT_EQ(0UL, buf.length());
ASSERT_TRUE(buf.empty());
}
TEST_F(IOBufTest, append) {
install_debug_allocator();
butil::IOBuf b;
ASSERT_EQ(0UL, b.length());
ASSERT_TRUE(b.empty());
ASSERT_EQ(-1, b.append(NULL));
ASSERT_EQ(0, b.append(""));
ASSERT_EQ(0, b.append(std::string()));
ASSERT_EQ(-1, b.append(NULL, 1));
ASSERT_EQ(0, b.append("dummy", 0));
ASSERT_EQ(0UL, b.length());
ASSERT_TRUE(b.empty());
ASSERT_EQ(0, b.append("1"));
ASSERT_EQ(1UL, b.length());
ASSERT_FALSE(b.empty());
ASSERT_EQ("1", to_str(b));
const std::string s = "22";
ASSERT_EQ(0, b.append(s));
ASSERT_EQ(3UL, b.length());
ASSERT_FALSE(b.empty());
ASSERT_EQ("122", to_str(b));
}
TEST_F(IOBufTest, appendv) {
install_debug_allocator();
butil::IOBuf b;
const_iovec vec[] = { {"hello1", 6}, {" world1", 7},
{"hello2", 6}, {" world2", 7},
{"hello3", 6}, {" world3", 7},
{"hello4", 6}, {" world4", 7},
{"hello5", 6}, {" world5", 7} };
ASSERT_EQ(0, b.appendv(vec, arraysize(vec)));
ASSERT_EQ("hello1 world1hello2 world2hello3 world3hello4 world4hello5 world5",
b.to_string());
// Make some iov_len shorter to test if iov_len works.
vec[2].iov_len = 4; // "hello2"
vec[5].iov_len = 3; // " world3"
b.clear();
ASSERT_EQ(0, b.appendv(vec, arraysize(vec)));
ASSERT_EQ("hello1 world1hell world2hello3 wohello4 world4hello5 world5",
b.to_string());
// Append some long stuff.
const size_t full_len = DEFAULT_PAYLOAD * 9;
char* str = (char*)malloc(full_len);
ASSERT_TRUE(str);
const size_t len1 = full_len / 6;
const size_t len2 = full_len / 3;
const size_t len3 = full_len - len1 - len2;
ASSERT_GT(len1, (size_t)DEFAULT_PAYLOAD);
ASSERT_GT(len2, (size_t)DEFAULT_PAYLOAD);
ASSERT_GT(len3, (size_t)DEFAULT_PAYLOAD);
ASSERT_EQ(full_len, len1 + len2 + len3);
for (size_t i = 0; i < full_len; ++i) {
str[i] = i * 7;
}
const_iovec vec2[] = {{str, len1},
{str + len1, len2},
{str + len1 + len2, len3}};
b.clear();
ASSERT_EQ(0, b.appendv(vec2, arraysize(vec2)));
ASSERT_EQ(full_len, b.size());
ASSERT_EQ(0, memcmp(str, b.to_string().data(), full_len));
}
TEST_F(IOBufTest, reserve) {
butil::IOBuf b;
ASSERT_EQ(butil::IOBuf::INVALID_AREA, b.reserve(0));
const size_t NRESERVED1 = 5;
const butil::IOBuf::Area a1 = b.reserve(NRESERVED1);
ASSERT_TRUE(a1 != butil::IOBuf::INVALID_AREA);
ASSERT_EQ(NRESERVED1, b.size());
b.append("hello world");
ASSERT_EQ(0, b.unsafe_assign(a1, "prefix")); // `x' will not be copied
ASSERT_EQ("prefihello world", b.to_string());
ASSERT_EQ((size_t)16, b.size());
// pop/append sth. from back-side and assign again.
ASSERT_EQ((size_t)5, b.pop_back(5));
ASSERT_EQ("prefihello ", b.to_string());
b.append("blahblahfoobar");
ASSERT_EQ(0, b.unsafe_assign(a1, "goodorbad")); // `x' will not be copied
ASSERT_EQ("goodohello blahblahfoobar", b.to_string());
// append a long string and assign again.
std::string s1(DEFAULT_PAYLOAD * 3, '\0');
for (size_t i = 0; i < s1.size(); ++i) {
s1[i] = i * 7;
}
ASSERT_EQ(DEFAULT_PAYLOAD * 3, s1.size());
// remove everything after reserved area
ASSERT_GE(b.size(), NRESERVED1);
b.pop_back(b.size() - NRESERVED1);
ASSERT_EQ(NRESERVED1, b.size());
b.append(s1);
ASSERT_EQ(0, b.unsafe_assign(a1, "appleblahblah"));
ASSERT_EQ("apple" + s1, b.to_string());
// Reserve long
b.pop_back(b.size() - NRESERVED1);
ASSERT_EQ(NRESERVED1, b.size());
const size_t NRESERVED2 = DEFAULT_PAYLOAD * 3;
const butil::IOBuf::Area a2 = b.reserve(NRESERVED2);
ASSERT_EQ(NRESERVED1 + NRESERVED2, b.size());
b.append(s1);
ASSERT_EQ(NRESERVED1 + NRESERVED2 + s1.size(), b.size());
std::string s2(NRESERVED2, 0);
for (size_t i = 0; i < s2.size(); ++i) {
s2[i] = i * 13;
}
ASSERT_EQ(NRESERVED2, s2.size());
ASSERT_EQ(0, b.unsafe_assign(a2, s2.data()));
ASSERT_EQ("apple" + s2 + s1, b.to_string());
ASSERT_EQ(0, b.unsafe_assign(a1, "orangeblahblah"));
ASSERT_EQ("orang" + s2 + s1, b.to_string());
}
struct FakeBlock {
int nshared;
FakeBlock() : nshared(1) {}
};
TEST_F(IOBufTest, iobuf_as_queue) {
install_debug_allocator();
// If INITIAL_CAP gets bigger, creating butil::IOBuf::Block are very
// small. Since We don't access butil::IOBuf::Block::data in this case.
// We replace butil::IOBuf::Block with FakeBlock with only nshared (in
// the same offset)
FakeBlock* blocks[butil::IOBuf::INITIAL_CAP+16];
const size_t NBLOCKS = ARRAY_SIZE(blocks);
butil::IOBuf::BlockRef r[NBLOCKS];
const size_t LENGTH = 7UL;
for (size_t i = 0; i < NBLOCKS; ++i) {
ASSERT_TRUE((blocks[i] = new FakeBlock));
r[i].offset = 1;
r[i].length = LENGTH;
r[i].block = (butil::IOBuf::Block*)blocks[i];
}
butil::IOBuf p;
// Empty
ASSERT_EQ(0UL, p._ref_num());
ASSERT_EQ(-1, p._pop_front_ref());
ASSERT_EQ(0UL, p.length());
// Add one ref
p._push_back_ref(r[0]);
ASSERT_EQ(1UL, p._ref_num());
ASSERT_EQ(LENGTH, p.length());
ASSERT_EQ(r[0], p._front_ref());
ASSERT_EQ(r[0], p._back_ref());
ASSERT_EQ(r[0], p._ref_at(0));
ASSERT_EQ(2, butil::iobuf::block_shared_count(r[0].block));
// Add second ref
p._push_back_ref(r[1]);
ASSERT_EQ(2UL, p._ref_num());
ASSERT_EQ(LENGTH*2, p.length());
ASSERT_EQ(r[0], p._front_ref());
ASSERT_EQ(r[1], p._back_ref());
ASSERT_EQ(r[0], p._ref_at(0));
ASSERT_EQ(r[1], p._ref_at(1));
ASSERT_EQ(2, butil::iobuf::block_shared_count(r[1].block));
// Pop a ref
ASSERT_EQ(0, p._pop_front_ref());
ASSERT_EQ(1UL, p._ref_num());
ASSERT_EQ(LENGTH, p.length());
ASSERT_EQ(r[1], p._front_ref());
ASSERT_EQ(r[1], p._back_ref());
ASSERT_EQ(r[1], p._ref_at(0));
//ASSERT_EQ(1, butil::iobuf::block_shared_count(r[0].block));
// Pop second
ASSERT_EQ(0, p._pop_front_ref());
ASSERT_EQ(0UL, p._ref_num());
ASSERT_EQ(0UL, p.length());
//ASSERT_EQ(1, r[1].block->nshared);
// Add INITIAL_CAP+2 refs, r[0] and r[1] are used, don't use again
for (size_t i = 0; i < butil::IOBuf::INITIAL_CAP+2; ++i) {
p._push_back_ref(r[i+2]);
ASSERT_EQ(i+1, p._ref_num());
ASSERT_EQ(p._ref_num()*LENGTH, p.length());
ASSERT_EQ(r[2], p._front_ref()) << i;
ASSERT_EQ(r[i+2], p._back_ref());
for (size_t j = 0; j <= i; j+=std::max(1UL, i/20) /*not check all*/) {
ASSERT_EQ(r[j+2], p._ref_at(j));
}
ASSERT_EQ(2, butil::iobuf::block_shared_count(r[i+2].block));
}
// Pop them all
const size_t saved_ref_num = p._ref_num();
while (p._ref_num() >= 2UL) {
const size_t last_ref_num = p._ref_num();
ASSERT_EQ(0, p._pop_front_ref());
ASSERT_EQ(last_ref_num, p._ref_num()+1);
ASSERT_EQ(p._ref_num()*LENGTH, p.length());
const size_t f = saved_ref_num - p._ref_num() + 2;
ASSERT_EQ(r[f], p._front_ref());
ASSERT_EQ(r[saved_ref_num+1], p._back_ref());
for (size_t j = 0; j < p._ref_num(); j += std::max(1UL, p._ref_num()/20)) {
ASSERT_EQ(r[j+f], p._ref_at(j));
}
//ASSERT_EQ(1, r[f-1].block->nshared);
}
ASSERT_EQ(1ul, p._ref_num());
// Pop last one
ASSERT_EQ(0, p._pop_front_ref());
ASSERT_EQ(0UL, p._ref_num());
ASSERT_EQ(0UL, p.length());
//ASSERT_EQ(1, r[saved_ref_num+1].block->nshared);
// Delete blocks
for (size_t i = 0; i < NBLOCKS; ++i) {
delete blocks[i];
}
}
TEST_F(IOBufTest, iobuf_sanity) {
install_debug_allocator();
LOG(INFO) << "sizeof(butil::IOBuf)=" << sizeof(butil::IOBuf)
<< " sizeof(IOPortal)=" << sizeof(butil::IOPortal);
butil::IOBuf b1;
std::string s1 = "hello world";
const char c1 = 'A';
const std::string s2 = "too simple";
std::string s1c = s1;
s1c.erase(0, 1);
// Append a c-std::string
ASSERT_EQ(0, b1.append(s1.c_str()));
ASSERT_EQ(s1.length(), b1.length());
ASSERT_EQ(s1, to_str(b1));
ASSERT_EQ(1UL, b1._ref_num());
// Append a char
ASSERT_EQ(0, b1.push_back(c1));
ASSERT_EQ(s1.length() + 1, b1.length());
ASSERT_EQ(s1+c1, to_str(b1));
ASSERT_EQ(1UL, b1._ref_num());
// Append a std::string
ASSERT_EQ(0, b1.append(s2));
ASSERT_EQ(s1.length() + 1 + s2.length(), b1.length());
ASSERT_EQ(s1+c1+s2, to_str(b1));
ASSERT_EQ(1UL, b1._ref_num());
// Pop first char
ASSERT_EQ(1UL, b1.pop_front(1));
ASSERT_EQ(1UL, b1._ref_num());
ASSERT_EQ(s1.length() + s2.length(), b1.length());
ASSERT_EQ(s1c+c1+s2, to_str(b1));
// Pop all
ASSERT_EQ(0UL, b1.pop_front(0));
ASSERT_EQ(s1.length() + s2.length(), b1.pop_front(b1.length()+1));
ASSERT_TRUE(b1.empty());
ASSERT_EQ(0UL, b1.length());
ASSERT_EQ(0UL, b1._ref_num());
ASSERT_EQ("", to_str(b1));
// Restore
ASSERT_EQ(0, b1.append(s1.c_str()));
ASSERT_EQ(0, b1.push_back(c1));
ASSERT_EQ(0, b1.append(s2));
// Cut first char
butil::IOBuf p;
b1.cutn(&p, 0);
b1.cutn(&p, 1);
ASSERT_EQ(s1.substr(0, 1), to_str(p));
ASSERT_EQ(s1c+c1+s2, to_str(b1));
// Cut another two and append to p
b1.cutn(&p, 2);
ASSERT_EQ(s1.substr(0, 3), to_str(p));
std::string s1d = s1;
s1d.erase(0, 3);
ASSERT_EQ(s1d+c1+s2, to_str(b1));
// Clear
b1.clear();
ASSERT_TRUE(b1.empty());
ASSERT_EQ(0UL, b1.length());
ASSERT_EQ(0UL, b1._ref_num());
ASSERT_EQ("", to_str(b1));
ASSERT_EQ(s1.substr(0, 3), to_str(p));
}
TEST_F(IOBufTest, copy_and_assign) {
install_debug_allocator();
const size_t TARGET_SIZE = butil::IOBuf::DEFAULT_BLOCK_SIZE * 2;
butil::IOBuf buf0;
buf0.append("hello");
ASSERT_EQ(1u, buf0._ref_num());
// Copy-construct from SmallView
butil::IOBuf buf1 = buf0;
ASSERT_EQ(1u, buf1._ref_num());
ASSERT_EQ(buf0, buf1);
buf1.resize(TARGET_SIZE, 'z');
ASSERT_LT(2u, buf1._ref_num());
ASSERT_EQ(TARGET_SIZE, buf1.size());
// Copy-construct from BigView
butil::IOBuf buf2 = buf1;
ASSERT_EQ(buf1, buf2);
// assign BigView to SmallView
butil::IOBuf buf3;
buf3 = buf1;
ASSERT_EQ(buf1, buf3);
// assign BigView to BigView
butil::IOBuf buf4;
buf4.resize(TARGET_SIZE, 'w');
ASSERT_NE(buf1, buf4);
buf4 = buf1;
ASSERT_EQ(buf1, buf4);
}
TEST_F(IOBufTest, compare) {
install_debug_allocator();
const char* SEED = "abcdefghijklmnqopqrstuvwxyz";
butil::IOBuf seedbuf;
seedbuf.append(SEED);
const int REP = 100;
butil::IOBuf b1;
for (int i = 0; i < REP; ++i) {
b1.append(seedbuf);
b1.append(SEED);
}
butil::IOBuf b2;
for (int i = 0; i < REP * 2; ++i) {
b2.append(SEED);
}
ASSERT_EQ(b1, b2);
butil::IOBuf b3 = b2;
b2.push_back('0');
ASSERT_NE(b1, b2);
ASSERT_EQ(b1, b3);
b1.clear();
b2.clear();
ASSERT_EQ(b1, b2);
}
TEST_F(IOBufTest, append_and_cut_it_all) {
butil::IOBuf b;
const size_t N = 32768UL;
for (size_t i = 0; i < N; ++i) {
ASSERT_EQ(0, b.push_back(i));
}
ASSERT_EQ(N, b.length());
butil::IOBuf p;
b.cutn(&p, N);
ASSERT_TRUE(b.empty());
ASSERT_EQ(N, p.length());
}
TEST_F(IOBufTest, copy_to) {
butil::IOBuf b;
const std::string seed = "abcdefghijklmnopqrstuvwxyz";
std::string src;
for (size_t i = 0; i < 1000; ++i) {
src.append(seed);
}
b.append(src);
ASSERT_GT(b.size(), DEFAULT_PAYLOAD);
std::string s1;
ASSERT_EQ(src.size(), b.copy_to(&s1));
ASSERT_EQ(src, s1);
std::string s2;
ASSERT_EQ(32u, b.copy_to(&s2, 32));
ASSERT_EQ(src.substr(0, 32), s2);
std::string s3;
const std::string expected = src.substr(DEFAULT_PAYLOAD - 1, 33);
ASSERT_EQ(33u, b.copy_to(&s3, 33, DEFAULT_PAYLOAD - 1));
ASSERT_EQ(expected, s3);
ASSERT_EQ(33u, b.append_to(&s3, 33, DEFAULT_PAYLOAD - 1));
ASSERT_EQ(expected + expected, s3);
butil::IOBuf b1;
ASSERT_EQ(src.size(), b.append_to(&b1));
ASSERT_EQ(src, b1.to_string());
butil::IOBuf b2;
ASSERT_EQ(32u, b.append_to(&b2, 32));
ASSERT_EQ(src.substr(0, 32), b2.to_string());
butil::IOBuf b3;
ASSERT_EQ(33u, b.append_to(&b3, 33, DEFAULT_PAYLOAD - 1));
ASSERT_EQ(expected, b3.to_string());
ASSERT_EQ(33u, b.append_to(&b3, 33, DEFAULT_PAYLOAD - 1));
ASSERT_EQ(expected + expected, b3.to_string());
}
TEST_F(IOBufTest, cut_by_single_text_delim) {
install_debug_allocator();
butil::IOBuf b;
butil::IOBuf p;
std::vector<butil::IOBuf> ps;
std::string s1 = "1234567\n12\n\n2567";
ASSERT_EQ(0, b.append(s1));
ASSERT_EQ(s1.length(), b.length());
for (; b.cut_until(&p, "\n") == 0; ) {
ps.push_back(p);
p.clear();
}
ASSERT_EQ(3UL, ps.size());
ASSERT_EQ("1234567", to_str(ps[0]));
ASSERT_EQ("12", to_str(ps[1]));
ASSERT_EQ("", to_str(ps[2]));
ASSERT_EQ("2567", to_str(b));
b.append("\n");
ASSERT_EQ(0, b.cut_until(&p, "\n"));
ASSERT_EQ("2567", to_str(p));
ASSERT_EQ("", to_str(b));
}
TEST_F(IOBufTest, cut_by_multiple_text_delim) {
install_debug_allocator();
butil::IOBuf b;
butil::IOBuf p;
std::vector<butil::IOBuf> ps;
std::string s1 = "\r\n1234567\r\n12\r\n\n\r2567";
ASSERT_EQ(0, b.append(s1));
ASSERT_EQ(s1.length(), b.length());
for (; b.cut_until(&p, "\r\n") == 0; ) {
ps.push_back(p);
p.clear();
}
ASSERT_EQ(3UL, ps.size());
ASSERT_EQ("", to_str(ps[0]));
ASSERT_EQ("1234567", to_str(ps[1]));
ASSERT_EQ("12", to_str(ps[2]));
ASSERT_EQ("\n\r2567", to_str(b));
b.append("\r\n");
ASSERT_EQ(0, b.cut_until(&p, "\r\n"));
ASSERT_EQ("\n\r2567", to_str(p));
ASSERT_EQ("", to_str(b));
}
TEST_F(IOBufTest, append_a_lot_and_cut_them_all) {
install_debug_allocator();
butil::IOBuf b;
butil::IOBuf p;
std::string s1 = "12345678901234567";
const size_t N = 10000;
for (size_t i= 0; i < N; ++i) {
b.append(s1);
}
ASSERT_EQ(N*s1.length(), b.length());
while (b.length() >= 7) {
b.cutn(&p, 7);
}
size_t remain = s1.length()*N % 7;
ASSERT_EQ(remain, b.length());
ASSERT_EQ(s1.substr(s1.length() - remain, remain), to_str(b));
ASSERT_EQ(s1.length()*N/7*7, p.length());
}
TEST_F(IOBufTest, cut_into_fd_tiny) {
install_debug_allocator();
butil::IOPortal b1, b2;
std::string ref;
int fds[2];
for (int j = 10; j > 0; --j) {
ref.push_back(j);
}
b1.append(ref);
ASSERT_EQ(1UL, b1.pop_front(1));
ref.erase(0, 1);
ASSERT_EQ(ref, to_str(b1));
LOG(INFO) << "ref.size=" << ref.size();
//ASSERT_EQ(0, pipe(fds));
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
butil::make_non_blocking(fds[0]);
butil::make_non_blocking(fds[1]);
while (!b1.empty() || b2.length() != ref.length()) {
size_t b1len = b1.length(), b2len = b2.length();
errno = 0;
printf("b1.length=%lu - %ld (%m), ",
b1len, b1.cut_into_file_descriptor(fds[1]));
printf("b2.length=%lu + %ld (%m)\n",
b2len, b2.append_from_file_descriptor(fds[0], LONG_MAX));
}
printf("b1.length=%lu, b2.length=%lu\n", b1.length(), b2.length());
ASSERT_EQ(ref, to_str(b2));
close(fds[0]);
close(fds[1]);
}
TEST_F(IOBufTest, cut_multiple_into_fd_tiny) {
install_debug_allocator();
butil::IOBuf* b1[10];
butil::IOPortal b2;
std::string ref;
int fds[2];
for (size_t j = 0; j < ARRAY_SIZE(b1); ++j) {
std::string s;
for (int i = 10; i > 0; --i) {
s.push_back(j * 10 + i);
}
ref.append(s);
butil::IOPortal* b = new butil::IOPortal();
b->append(s);
b1[j] = b;
}
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
butil::make_non_blocking(fds[0]);
butil::make_non_blocking(fds[1]);
ASSERT_EQ((ssize_t)ref.length(),
butil::IOBuf::cut_multiple_into_file_descriptor(
fds[1], b1, ARRAY_SIZE(b1)));
for (size_t j = 0; j < ARRAY_SIZE(b1); ++j) {
ASSERT_TRUE(b1[j]->empty());
delete (butil::IOPortal*)b1[j];
b1[j] = NULL;
}
ASSERT_EQ((ssize_t)ref.length(),
b2.append_from_file_descriptor(fds[0], LONG_MAX));
ASSERT_EQ(ref, to_str(b2));
close(fds[0]);
close(fds[1]);
}
TEST_F(IOBufTest, cut_into_fd_a_lot_of_data) {
install_debug_allocator();
butil::IOPortal b0, b1, b2;
std::string s, ref;
int fds[2];
for (int j = rand()%7777+300000; j > 0; --j) {
ref.push_back(j);
s.push_back(j);
if (s.length() == 1024UL || j == 1) {
b0.append(s);
ref.append("tailing");
b0.cutn(&b1, b0.length());
ASSERT_EQ(0, b1.append("tailing"));
s.clear();
}
}
ASSERT_EQ(ref.length(), b1.length());
ASSERT_EQ(ref, to_str(b1));
ASSERT_TRUE(b0.empty());
LOG(INFO) << "ref.size=" << ref.size();
//ASSERT_EQ(0, pipe(fds));
ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, fds));
butil::make_non_blocking(fds[0]);
butil::make_non_blocking(fds[1]);
const int sockbufsize = 16 * 1024 - 17;
ASSERT_EQ(0, setsockopt(fds[1], SOL_SOCKET, SO_SNDBUF,
(const char*)&sockbufsize, sizeof(int)));
ASSERT_EQ(0, setsockopt(fds[0], SOL_SOCKET, SO_RCVBUF,
(const char*)&sockbufsize, sizeof(int)));
while (!b1.empty() || b2.length() != ref.length()) {
size_t b1len = b1.length(), b2len = b2.length();
errno = 0;
printf("b1.length=%lu - %ld (%m), ", b1len, b1.cut_into_file_descriptor(fds[1]));
printf("b2.length=%lu + %ld (%m)\n", b2len, b2.append_from_file_descriptor(fds[0], LONG_MAX));
}
printf("b1.length=%lu, b2.length=%lu\n", b1.length(), b2.length());
ASSERT_EQ(ref, to_str(b2));
close(fds[0]);
close(fds[1]);
}
TEST_F(IOBufTest, cut_by_delim_perf) {
butil::iobuf::reset_blockmem_allocate_and_deallocate();
butil::IOBuf b;
butil::IOBuf p;
std::vector<butil::IOBuf> ps;
std::string s1 = "123456789012345678901234567890\n";
const size_t N = 100000;
for (size_t i = 0; i < N; ++i) {
ASSERT_EQ(0, b.append(s1));
}
ASSERT_EQ(N * s1.length(), b.length());
butil::Timer t;
//ProfilerStart("cutd.prof");
t.start();
for (; b.cut_until(&p, "\n") == 0; ) { }
t.stop();
//ProfilerStop();
LOG(INFO) << "IOPortal::cut_by_delim takes "
<< t.n_elapsed()/N << "ns, tp="
<< s1.length() * N * 1000.0 / t.n_elapsed () << "MB/s";
show_prof_and_rm("test_iobuf", "cutd.prof", 10);
}
TEST_F(IOBufTest, cut_perf) {
butil::iobuf::reset_blockmem_allocate_and_deallocate();
butil::IOBuf b;
butil::IOBuf p;
const size_t length = 60000000UL;
const size_t REP = 10;
butil::Timer t;
std::string s = "1234567890";
const bool push_char = false;
if (!push_char) {
//ProfilerStart("iobuf_append.prof");
t.start();
for (size_t j = 0; j < REP; ++j) {
b.clear();
for (size_t i = 0; i < length/s.length(); ++i) {
b.append(s);
}
}
t.stop();
//ProfilerStop();
LOG(INFO) << "IOPortal::append(std::string_" << s.length() << ") takes "
<< t.n_elapsed() * s.length() / length / REP << "ns, tp="
<< REP * length * 1000.0 / t.n_elapsed () << "MB/s";
} else {
//ProfilerStart("iobuf_pushback.prof");
t.start();
for (size_t i = 0; i < length; ++i) {
b.push_back(i);
}
t.stop();
//ProfilerStop();
LOG(INFO) << "IOPortal::push_back(char) takes "
<< t.n_elapsed() / length << "ns, tp="
<< length * 1000.0 / t.n_elapsed () << "MB/s";
}
ASSERT_EQ(length, b.length());
size_t w[3] = { 16, 128, 1024 };
//char name[32];
for (size_t i = 0; i < ARRAY_SIZE(w); ++i) {
// snprintf(name, sizeof(name), "iobuf_cut%lu.prof", w[i]);
// ProfilerStart(name);
t.start ();
while (b.length() >= w[i]+12) {
b.cutn(&p, 12);
b.cutn(&p, w[i]);
}
t.stop ();
//ProfilerStop();
LOG(INFO) << "IOPortal::cutn(12+" << w[i] << ") takes "
<< t.n_elapsed()*(w[i]+12)/length << "ns, tp="
<< length * 1000.0 / t.n_elapsed () << "MB/s";
ASSERT_LT(b.length(), w[i]+12);
t.start();
b.append(p);
t.stop();
LOG(INFO) << "IOPortal::append(butil::IOBuf) takes "
<< t.n_elapsed ()/p._ref_num() << "ns, tp="
<< length * 1000.0 / t.n_elapsed () << "MB/s";
p.clear();
ASSERT_EQ(length, b.length());
}
show_prof_and_rm("test_iobuf", "./iobuf_append.prof", 10);
show_prof_and_rm("test_iobuf", "./iobuf_pushback.prof", 10);
}
TEST_F(IOBufTest, append_store_append_cut) {
butil::iobuf::reset_blockmem_allocate_and_deallocate();
std::string ref;
ref.resize(rand()%376813+19777777);
for (size_t j = 0; j < ref.size(); ++j) {
ref[j] = j;
}
butil::IOPortal b1, b2;
std::vector<butil::IOBuf> ps;
ssize_t nr;
size_t HINT = 16*1024UL;
butil::Timer t;
size_t w[3] = { 16, 128, 1024 };
char name[64];
char profname[64];
char cmd[512];
bool write_to_dev_null = true;
size_t nappend, ncut;
butil::TempFile f;
ASSERT_EQ(0, f.save_bin(ref.data(), ref.length()));
for (size_t i = 0; i < ARRAY_SIZE(w); ++i) {
ps.reserve(ref.size()/(w[i]+12) + 1);
// LOG(INFO) << "ps.cap=" << ps.capacity();
const int ifd = open(f.fname(), O_RDONLY);
ASSERT_TRUE(ifd > 0);
if (write_to_dev_null) {
snprintf(name, sizeof(name), "/dev/null");
} else {
snprintf(name, sizeof(name), "iobuf_asac%lu.output", w[i]);
snprintf(cmd, sizeof(cmd), "cmp %s %s", f.fname(), name);
}
const int ofd = open(name, O_CREAT | O_WRONLY, 0666);
ASSERT_TRUE(ofd > 0) << strerror(errno);
snprintf(profname, sizeof(profname), "iobuf_asac%lu.prof", w[i]);
//ProfilerStart(profname);
t.start();
nappend = ncut = 0;
while ((nr = b1.append_from_file_descriptor(ifd, HINT)) > 0) {
++nappend;
while (b1.length() >= w[i] + 12) {
butil::IOBuf p;
b1.cutn(&p, 12);
b1.cutn(&p, w[i]);
ps.push_back(p);
}
}
for (size_t j = 0; j < ps.size(); ++j) {
b2.append(ps[j]);
if (b2.length() >= HINT) {
b2.cut_into_file_descriptor(ofd);
}
}
b2.cut_into_file_descriptor(ofd);
b1.cut_into_file_descriptor(ofd);
close(ifd);
close(ofd);
t.stop();
//ProfilerStop();
ASSERT_TRUE(b1.empty());
ASSERT_TRUE(b2.empty());
//LOG(INFO) << "ps.size=" << ps.size();
ps.clear();
LOG(INFO) << "Bandwidth of append(" << f.fname() << ")->cut(12+" << w[i]
<< ")->store->append->cut(" << name << ") is "
<< ref.length() * 1000.0 / t.n_elapsed () << "MB/s";
if (!write_to_dev_null) {
ASSERT_EQ(0, system(cmd));
}
if (!write_to_dev_null) {
remove(name);
}
}
for (size_t i = 0; i < ARRAY_SIZE(w); ++i) {
snprintf(name, sizeof(name), "iobuf_asac%lu.prof", w[i]);
show_prof_and_rm("test_iobuf", name, 10);
}
}
TEST_F(IOBufTest, conversion_with_protobuf) {
const int REP = 1000;
proto::Misc m1;
m1.set_required_enum(proto::CompressTypeGzip);
m1.set_required_uint64(0xdeadbeef);
m1.set_optional_uint64(10000);
m1.set_required_string("hello iobuf");
m1.set_optional_string("hello iobuf again");
for (int i = 0; i < REP; ++i) {
char buf[16];
snprintf(buf, sizeof(buf), "value%d", i);
m1.add_repeated_string(buf);
}
m1.set_required_bool(true);
m1.set_required_int32(0xbeefdead);
butil::IOBuf buf;
const std::string header("just-make-sure-wrapper-does-not-clear-IOBuf");
ASSERT_EQ(0, buf.append(header));
butil::IOBufAsZeroCopyOutputStream out_wrapper(&buf);
ASSERT_EQ(0, out_wrapper.ByteCount());
ASSERT_TRUE(m1.SerializeToZeroCopyStream(&out_wrapper));
ASSERT_EQ((size_t)m1.ByteSize() + header.size(), buf.length());
ASSERT_EQ(m1.ByteSize(), out_wrapper.ByteCount());
ASSERT_EQ(header.size(), buf.pop_front(header.size()));
butil::IOBufAsZeroCopyInputStream in_wrapper(buf);
ASSERT_EQ(0, in_wrapper.ByteCount());
{
const void* dummy_blk = NULL;
int dummy_size = 0;
ASSERT_TRUE(in_wrapper.Next(&dummy_blk, &dummy_size));
ASSERT_EQ(dummy_size, in_wrapper.ByteCount());
in_wrapper.BackUp(1);
ASSERT_EQ(dummy_size - 1, in_wrapper.ByteCount());
const void* dummy_blk2 = NULL;
int dummy_size2 = 0;
ASSERT_TRUE(in_wrapper.Next(&dummy_blk2, &dummy_size2));
ASSERT_EQ(1, dummy_size2);
ASSERT_EQ((char*)dummy_blk + dummy_size - 1, (char*)dummy_blk2);
ASSERT_EQ(dummy_size, in_wrapper.ByteCount());
in_wrapper.BackUp(dummy_size);
ASSERT_EQ(0, in_wrapper.ByteCount());
}
proto::Misc m2;
ASSERT_TRUE(m2.ParseFromZeroCopyStream(&in_wrapper));
ASSERT_EQ(m1.ByteSize(), in_wrapper.ByteCount());
ASSERT_EQ(m2.ByteSize(), in_wrapper.ByteCount());
ASSERT_EQ(m1.required_enum(), m2.required_enum());
ASSERT_FALSE(m2.has_optional_enum());
ASSERT_EQ(m1.required_uint64(), m2.required_uint64());
ASSERT_TRUE(m2.has_optional_uint64());
ASSERT_EQ(m1.optional_uint64(), m2.optional_uint64());
ASSERT_EQ(m1.required_string(), m2.required_string());
ASSERT_TRUE(m2.has_optional_string());
ASSERT_EQ(m1.optional_string(), m2.optional_string());
ASSERT_EQ(REP, m2.repeated_string_size());
for (int i = 0; i < REP; ++i) {
ASSERT_EQ(m1.repeated_string(i), m2.repeated_string(i));
}
ASSERT_EQ(m1.required_bool(), m2.required_bool());
ASSERT_FALSE(m2.has_optional_bool());
ASSERT_EQ(m1.required_int32(), m2.required_int32());
ASSERT_FALSE(m2.has_optional_int32());
}
TEST_F(IOBufTest, extended_backup) {
for (int i = 0; i < 2; ++i) {
std::cout << "i=" << i << std::endl;
// Consume the left TLS block so that cases are easier to check.
butil::iobuf::remove_tls_block_chain();
butil::IOBuf src;
const int BLKSIZE = (i == 0 ? 1024 : butil::IOBuf::DEFAULT_BLOCK_SIZE);
const int PLDSIZE = BLKSIZE - BLOCK_OVERHEAD;
butil::IOBufAsZeroCopyOutputStream out_stream1(&src, BLKSIZE);
butil::IOBufAsZeroCopyOutputStream out_stream2(&src);
butil::IOBufAsZeroCopyOutputStream & out_stream =
(i == 0 ? out_stream1 : out_stream2);
void* blk1 = NULL;
int size1 = 0;
ASSERT_TRUE(out_stream.Next(&blk1, &size1));
ASSERT_EQ(PLDSIZE, size1);
ASSERT_EQ(size1, out_stream.ByteCount());
void* blk2 = NULL;
int size2 = 0;
ASSERT_TRUE(out_stream.Next(&blk2, &size2));
ASSERT_EQ(PLDSIZE, size2);
ASSERT_EQ(size1 + size2, out_stream.ByteCount());
// BackUp a size that's valid for all ZeroCopyOutputStream
out_stream.BackUp(PLDSIZE / 2);
ASSERT_EQ(size1 + size2 - PLDSIZE / 2, out_stream.ByteCount());
void* blk3 = NULL;
int size3 = 0;
ASSERT_TRUE(out_stream.Next(&blk3, &size3));
ASSERT_EQ((char*)blk2 + PLDSIZE / 2, blk3);
ASSERT_EQ(PLDSIZE / 2, size3);
ASSERT_EQ(size1 + size2, out_stream.ByteCount());
// BackUp a size that's undefined in regular ZeroCopyOutputStream
out_stream.BackUp(PLDSIZE * 2);
ASSERT_EQ(0, out_stream.ByteCount());
void* blk4 = NULL;
int size4 = 0;
ASSERT_TRUE(out_stream.Next(&blk4, &size4));
ASSERT_EQ(PLDSIZE, size4);
ASSERT_EQ(size4, out_stream.ByteCount());
if (i == 1) {
ASSERT_EQ(blk1, blk4);
}
void* blk5 = NULL;
int size5 = 0;
ASSERT_TRUE(out_stream.Next(&blk5, &size5));
ASSERT_EQ(PLDSIZE, size5);
ASSERT_EQ(size4 + size5, out_stream.ByteCount());
if (i == 1) {
ASSERT_EQ(blk2, blk5);
}
}
}
TEST_F(IOBufTest, backup_iobuf_never_called_next) {
{
// Consume the left TLS block so that later cases are easier
// to check.
butil::IOBuf dummy;
butil::IOBufAsZeroCopyOutputStream dummy_stream(&dummy);
void* dummy_data = NULL;
int dummy_size = 0;
ASSERT_TRUE(dummy_stream.Next(&dummy_data, &dummy_size));
}
butil::IOBuf src;
const size_t N = DEFAULT_PAYLOAD * 2;
src.resize(N);
ASSERT_EQ(2u, src.backing_block_num());
ASSERT_EQ(N, src.size());
butil::IOBufAsZeroCopyOutputStream out_stream(&src);
out_stream.BackUp(1); // also succeed.
ASSERT_EQ(-1, out_stream.ByteCount());
ASSERT_EQ(DEFAULT_PAYLOAD * 2 - 1, src.size());
ASSERT_EQ(2u, src.backing_block_num());
void* data0 = NULL;
int size0 = 0;
ASSERT_TRUE(out_stream.Next(&data0, &size0));
ASSERT_EQ(1, size0);
ASSERT_EQ(0, out_stream.ByteCount());
ASSERT_EQ(2u, src.backing_block_num());
void* data1 = NULL;
int size1 = 0;
ASSERT_TRUE(out_stream.Next(&data1, &size1));
ASSERT_EQ(size1, out_stream.ByteCount());
ASSERT_EQ(3u, src.backing_block_num());
ASSERT_EQ(N + size1, src.size());
void* data2 = NULL;
int size2 = 0;
ASSERT_TRUE(out_stream.Next(&data2, &size2));
ASSERT_EQ(size1 + size2, out_stream.ByteCount());
ASSERT_EQ(4u, src.backing_block_num());
ASSERT_EQ(N + size1 + size2, src.size());
LOG(INFO) << "Backup1";
out_stream.BackUp(size1); // intended size1, not size2 to make this BackUp
// to cross boundary of blocks.
ASSERT_EQ(size2, out_stream.ByteCount());
ASSERT_EQ(N + size2, src.size());
LOG(INFO) << "Backup2";
out_stream.BackUp(size2);
ASSERT_EQ(0, out_stream.ByteCount());
ASSERT_EQ(N, src.size());
}
void *backup_thread(void *arg) {
butil::IOBufAsZeroCopyOutputStream *wrapper =
(butil::IOBufAsZeroCopyOutputStream *)arg;
wrapper->BackUp(1024);
return NULL;
}
TEST_F(IOBufTest, backup_in_another_thread) {
butil::IOBuf buf;
butil::IOBufAsZeroCopyOutputStream wrapper(&buf);
size_t alloc_size = 0;
for (int i = 0; i < 10; ++i) {
void *data;
int len;
ASSERT_TRUE(wrapper.Next(&data, &len));
alloc_size += len;
}
ASSERT_EQ(alloc_size, buf.length());
for (int i = 0; i < 10; ++i) {
void *data;
int len;
ASSERT_TRUE(wrapper.Next(&data, &len));
alloc_size += len;
pthread_t tid;
pthread_create(&tid, NULL, backup_thread, &wrapper);
pthread_join(tid, NULL);
}
ASSERT_EQ(alloc_size - 1024 * 10, buf.length());
}
TEST_F(IOBufTest, own_block) {
butil::IOBuf buf;
const ssize_t BLOCK_SIZE = 1024;
butil::IOBuf::Block *saved_tls_block = butil::iobuf::get_tls_block_head();
butil::IOBufAsZeroCopyOutputStream wrapper(&buf, BLOCK_SIZE);
int alloc_size = 0;
for (int i = 0; i < 100; ++i) {
void *data;
int size;
ASSERT_TRUE(wrapper.Next(&data, &size));
alloc_size += size;
if (size > 1) {
wrapper.BackUp(1);
alloc_size -= 1;
}
}
ASSERT_EQ(static_cast<size_t>(alloc_size), buf.length());
ASSERT_EQ(saved_tls_block, butil::iobuf::get_tls_block_head());
ASSERT_EQ(butil::iobuf::block_cap(buf._front_ref().block), BLOCK_SIZE - BLOCK_OVERHEAD);
}
struct Foo1 {
explicit Foo1(int x2) : x(x2) {}
int x;
};
struct Foo2 {
std::vector<Foo1> foo1;
};
std::ostream& operator<<(std::ostream& os, const Foo1& foo1) {
return os << "foo1.x=" << foo1.x;
}
std::ostream& operator<<(std::ostream& os, const Foo2& foo2) {
for (size_t i = 0; i < foo2.foo1.size(); ++i) {
os << "foo2[" << i << "]=" << foo2.foo1[i];
}
return os;
}
TEST_F(IOBufTest, as_ostream) {
butil::iobuf::reset_blockmem_allocate_and_deallocate();
butil::IOBufBuilder builder;
LOG(INFO) << "sizeof(IOBufBuilder)=" << sizeof(builder) << std::endl
<< "sizeof(IOBuf)=" << sizeof(butil::IOBuf) << std::endl
<< "sizeof(IOBufAsZeroCopyOutputStream)="
<< sizeof(butil::IOBufAsZeroCopyOutputStream) << std::endl
<< "sizeof(ZeroCopyStreamAsStreamBuf)="
<< sizeof(butil::ZeroCopyStreamAsStreamBuf) << std::endl
<< "sizeof(ostream)=" << sizeof(std::ostream);
int x = -1;
builder << 2 << " " << x << " " << 1.1 << " hello ";
ASSERT_EQ("2 -1 1.1 hello ", builder.buf().to_string());
builder << "world!";
ASSERT_EQ("2 -1 1.1 hello world!", builder.buf().to_string());
builder.buf().clear();
builder << "world!";
ASSERT_EQ("world!", builder.buf().to_string());
Foo2 foo2;
for (int i = 0; i < 10000; ++i) {
foo2.foo1.push_back(Foo1(i));
}
builder.buf().clear();
builder << "<before>" << foo2 << "<after>";
std::ostringstream oss;
oss << "<before>" << foo2 << "<after>";
ASSERT_EQ(oss.str(), builder.buf().to_string());
butil::IOBuf target;
builder.move_to(target);
ASSERT_TRUE(builder.buf().empty());
ASSERT_EQ(oss.str(), target.to_string());
std::ostringstream oss2;
oss2 << target;
ASSERT_EQ(oss.str(), oss2.str());
}
TEST_F(IOBufTest, append_from_fd_with_offset) {
butil::TempFile file;
file.save("dummy");
butil::fd_guard fd(open(file.fname(), O_RDWR | O_TRUNC));
ASSERT_TRUE(fd >= 0) << file.fname() << ' ' << berror();
butil::IOPortal buf;
char dummy[10 * 1024];
buf.append(dummy, sizeof(dummy));
ASSERT_EQ((ssize_t)sizeof(dummy), buf.cut_into_file_descriptor(fd));
for (size_t i = 0; i < sizeof(dummy); ++i) {
butil::IOPortal b0;
ASSERT_EQ(sizeof(dummy) - i, (size_t)b0.pappend_from_file_descriptor(fd, i, sizeof(dummy))) << berror();
char tmp[sizeof(dummy)];
ASSERT_EQ(0, memcmp(dummy + i, b0.fetch(tmp, b0.length()), b0.length()));
}
}
static butil::atomic<int> s_nthread(0);
static long number_per_thread = 1024;
void* cut_into_fd(void* arg) {
int fd = (int)(long)arg;
const long start_num = number_per_thread *
s_nthread.fetch_add(1, butil::memory_order_relaxed);
off_t offset = start_num * sizeof(int);
for (int i = 0; i < number_per_thread; ++i) {
int to_write = start_num + i;
butil::IOBuf out;
out.append(&to_write, sizeof(int));
CHECK_EQ(out.pcut_into_file_descriptor(fd, offset + sizeof(int) * i),
(ssize_t)sizeof(int));
}
return NULL;
}
TEST_F(IOBufTest, cut_into_fd_with_offset_multithreaded) {
s_nthread.store(0);
number_per_thread = 10240;
pthread_t threads[8];
long fd = open(".out.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
ASSERT_TRUE(fd >= 0) << berror();
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
ASSERT_EQ(0, pthread_create(&threads[i], NULL, cut_into_fd, (void*)fd));
}
for (size_t i = 0; i < ARRAY_SIZE(threads); ++i) {
pthread_join(threads[i], NULL);
}
for (int i = 0; i < number_per_thread * (int)ARRAY_SIZE(threads); ++i) {
off_t offset = i * sizeof(int);
butil::IOPortal in;
ASSERT_EQ((ssize_t)sizeof(int), in.pappend_from_file_descriptor(fd, offset, sizeof(int)));
int j;
ASSERT_EQ(sizeof(j), in.cutn(&j, sizeof(j)));
ASSERT_EQ(i, j);
}
}
TEST_F(IOBufTest, slice) {
size_t N = 100000;
std::string expected;
expected.reserve(N);
butil::IOBuf buf;
for (size_t i = 0; i < N; ++i) {
expected.push_back(i % 26 + 'a');
buf.push_back(i % 26 + 'a');
}
const size_t block_count = buf.backing_block_num();
std::string actual;
actual.reserve(expected.size());
for (size_t i = 0; i < block_count; ++i) {
butil::StringPiece p = buf.backing_block(i);
ASSERT_FALSE(p.empty());
actual.append(p.data(), p.size());
}
ASSERT_TRUE(expected == actual);
}
TEST_F(IOBufTest, swap) {
butil::IOBuf a;
a.append("I'am a");
butil::IOBuf b;
b.append("I'am b");
std::swap(a, b);
ASSERT_TRUE(a.equals("I'am b"));
ASSERT_TRUE(b.equals("I'am a"));
}
TEST_F(IOBufTest, resize) {
butil::IOBuf a;
a.resize(100);
std::string as;
as.resize(100);
butil::IOBuf b;
b.resize(100, 'b');
std::string bs;
bs.resize(100, 'b');
ASSERT_EQ(100u, a.length());
ASSERT_EQ(100u, b.length());
ASSERT_TRUE(a.equals(as));
ASSERT_TRUE(b.equals(bs));
}
TEST_F(IOBufTest, iterate_bytes) {
butil::IOBuf a;
a.append("hello world");
std::string saved_a = a.to_string();
size_t n = 0;
butil::IOBufBytesIterator it(a);
for (; it != NULL; ++it, ++n) {
ASSERT_EQ(saved_a[n], *it);
}
ASSERT_EQ(saved_a.size(), n);
ASSERT_TRUE(saved_a == a);
// append more to the iobuf, iterator should still be ended.
a.append(", this is iobuf");
ASSERT_TRUE(it == NULL);
// append more-than-one-block data to the iobuf
for (int i = 0; i < 1024; ++i) {
a.append("ab33jm4hgaklkv;2[25lj4lkj312kl4j321kl4j3k");
}
saved_a = a.to_string();
n = 0;
for (butil::IOBufBytesIterator it2(a); it2 != NULL; it2++/*intended post++*/, ++n) {
ASSERT_EQ(saved_a[n], *it2);
}
ASSERT_EQ(saved_a.size(), n);
ASSERT_TRUE(saved_a == a);
}
TEST_F(IOBufTest, appender) {
butil::IOBufAppender appender;
ASSERT_EQ(0, appender.append("hello", 5));
ASSERT_EQ("hello", appender.buf());
ASSERT_EQ(0, appender.push_back(' '));
ASSERT_EQ(0, appender.append("world", 5));
ASSERT_EQ("hello world", appender.buf());
butil::IOBuf buf2;
appender.move_to(buf2);
ASSERT_EQ("", appender.buf());
ASSERT_EQ("hello world", buf2);
std::string str;
for (int i = 0; i < 10000; ++i) {
char buf[128];
int len = snprintf(buf, sizeof(buf), "1%d2%d3%d4%d5%d", i, i, i, i, i);
appender.append(buf, len);
str.append(buf, len);
}
ASSERT_EQ(str, appender.buf());
butil::IOBuf buf3;
appender.move_to(buf3);
ASSERT_EQ("", appender.buf());
ASSERT_EQ(str, buf3);
}
TEST_F(IOBufTest, appender_perf) {
const size_t N1 = 100000;
butil::Timer tm1;
tm1.start();
butil::IOBuf buf1;
for (size_t i = 0; i < N1; ++i) {
buf1.push_back(i);
}
tm1.stop();
butil::Timer tm2;
tm2.start();
butil::IOBufAppender appender1;
for (size_t i = 0; i < N1; ++i) {
appender1.push_back(i);
}
tm2.stop();
LOG(INFO) << "IOBuf.push_back=" << tm1.n_elapsed() / N1
<< "ns IOBufAppender.push_back=" << tm2.n_elapsed() / N1
<< "ns";
const size_t N2 = 50000;
const std::string s = "a repeatly appended string";
std::string str2;
butil::IOBuf buf2;
tm1.start();
for (size_t i = 0; i < N2; ++i) {
buf2.append(s);
}
tm1.stop();
tm2.start();
butil::IOBufAppender appender2;
for (size_t i = 0; i < N2; ++i) {
appender2.append(s);
}
tm2.stop();
butil::Timer tm3;
tm3.start();
for (size_t i = 0; i < N2; ++i) {
str2.append(s);
}
tm3.stop();
LOG(INFO) << "IOBuf.append=" << tm1.n_elapsed() / N2
<< "ns IOBufAppender.append=" << tm2.n_elapsed() / N2
<< "ns string.append=" << tm3.n_elapsed() / N2
<< "ns (string-length=" << s.size() << ')';
}
TEST_F(IOBufTest, printed_as_binary) {
butil::IOBuf buf;
std::string str;
for (int i = 0; i < 256; ++i) {
buf.push_back((char)i);
str.push_back((char)i);
}
const char* const OUTPUT =
"\\00\\01\\02\\03\\04\\05\\06\\07\\b\\t\\n\\0B\\0C\\r\\0E\\0F"
"\\10\\11\\12\\13\\14\\15\\16\\17\\18\\19\\1A\\1B\\1C\\1D\\1E"
"\\1F !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUV"
"WXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\7F\\80\\81\\82"
"\\83\\84\\85\\86\\87\\88\\89\\8A\\8B\\8C\\8D\\8E\\8F\\90\\91"
"\\92\\93\\94\\95\\96\\97\\98\\99\\9A\\9B\\9C\\9D\\9E\\9F\\A0"
"\\A1\\A2\\A3\\A4\\A5\\A6\\A7\\A8\\A9\\AA\\AB\\AC\\AD\\AE\\AF"
"\\B0\\B1\\B2\\B3\\B4\\B5\\B6\\B7\\B8\\B9\\BA\\BB\\BC\\BD\\BE"
"\\BF\\C0\\C1\\C2\\C3\\C4\\C5\\C6\\C7\\C8\\C9\\CA\\CB\\CC\\CD"
"\\CE\\CF\\D0\\D1\\D2\\D3\\D4\\D5\\D6\\D7\\D8\\D9\\DA\\DB\\DC"
"\\DD\\DE\\DF\\E0\\E1\\E2\\E3\\E4\\E5\\E6\\E7\\E8\\E9\\EA\\EB"
"\\EC\\ED\\EE\\EF\\F0\\F1\\F2\\F3\\F4\\F5\\F6\\F7\\F8\\F9\\FA"
"\\FB\\FC\\FD\\FE\\FF";
std::ostringstream os;
os << butil::ToPrintable(buf, 256);
ASSERT_STREQ(OUTPUT, os.str().c_str());
os.str("");
os << butil::ToPrintable(str, 256);
ASSERT_STREQ(OUTPUT, os.str().c_str());
}
TEST_F(IOBufTest, copy_to_string_from_iterator) {
butil::IOBuf b0;
for (size_t i = 0; i < 1 * 1024 * 1024lu; ++i) {
b0.push_back(butil::fast_rand_in('a', 'z'));
}
butil::IOBuf b1(b0);
butil::IOBufBytesIterator iter(b0);
size_t nc = 0;
while (nc < b0.length()) {
size_t to_copy = butil::fast_rand_in(1024lu, 64 * 1024lu);
butil::IOBuf b;
b1.cutn(&b, to_copy);
std::string s;
const size_t copied = iter.copy_and_forward(&s, to_copy);
ASSERT_GT(copied, 0u);
ASSERT_TRUE(b.equals(s));
nc += copied;
}
ASSERT_EQ(nc, b0.length());
}
static void* my_free_params = NULL;
static void my_free(void* m) {
free(m);
my_free_params = m;
}
TEST_F(IOBufTest, append_user_data_and_consume) {
butil::IOBuf b0;
const int REP = 16;
const size_t len = REP * 256;
char* data = (char*)malloc(len);
for (int i = 0; i < 256; ++i) {
for (int j = 0; j < REP; ++j) {
data[i * REP + j] = (char)i;
}
}
my_free_params = NULL;
ASSERT_EQ(0, b0.append_user_data(data, len, my_free));
ASSERT_EQ(1UL, b0._ref_num());
butil::IOBuf::BlockRef r = b0._front_ref();
ASSERT_EQ(1, butil::iobuf::block_shared_count(r.block));
ASSERT_EQ(len, b0.size());
std::string out;
ASSERT_EQ(len, b0.cutn(&out, len));
ASSERT_TRUE(b0.empty());
ASSERT_EQ(data, my_free_params);
ASSERT_EQ(len, out.size());
// note: cannot memcmp with data which is already free-ed
for (int i = 0; i < 256; ++i) {
for (int j = 0; j < REP; ++j) {
ASSERT_EQ((char)i, out[i * REP + j]);
}
}
}
TEST_F(IOBufTest, append_stateful_user_data) {
butil::IOBuf b0;
const int REP = 16;
const size_t len = REP * 256;
std::shared_ptr<char> mem(new char[len], std::default_delete<char[]>());
std::weak_ptr<char> weaker = mem;
ASSERT_EQ(1, mem.use_count());
char* data = mem.get();
for (int i = 0; i < 256; ++i) {
for (int j = 0; j < REP; ++j) {
data[i * REP + j] = (char)i;
}
}
int incr_upon_destructrion = 0;
struct Deleter {
Deleter(std::shared_ptr<char> partial, int& incr) : partial(std::move(partial)), incr(&incr), allow_incr(false) {}
~Deleter() {
if (allow_incr) (*incr)++;
}
Deleter(const Deleter&) { std::abort(); /*if copy, then crash*/ }
Deleter(Deleter&&) noexcept = default;
void operator()(void*) {
partial.reset();
allow_incr = true;
}
std::shared_ptr<char> partial;
int* incr;
bool allow_incr;
};
for (int i = 0; i < 256; i++) {
std::shared_ptr<char> ptr(mem, data + i * REP);
ASSERT_EQ(0, b0.append_user_data(data + i * REP, REP, Deleter{std::move(ptr), incr_upon_destructrion}));
}
ASSERT_EQ(256, b0._ref_num());
ASSERT_EQ(257, mem.use_count());
mem.reset();
butil::IOBuf::BlockRef r = b0._front_ref();
ASSERT_EQ(1, butil::iobuf::block_shared_count(r.block));
ASSERT_EQ(len, b0.size());
std::string out;
ASSERT_EQ(len, b0.cutn(&out, len));
ASSERT_TRUE(b0.empty());
ASSERT_TRUE(weaker.expired());
ASSERT_EQ(256, incr_upon_destructrion);
ASSERT_EQ(len, out.size());
// note: cannot memcmp with data which is already free-ed
for (int i = 0; i < 256; ++i) {
for (int j = 0; j < REP; ++j) {
ASSERT_EQ((char)i, out[i * REP + j]);
}
}
}
TEST_F(IOBufTest, append_user_data_and_share) {
butil::IOBuf b0;
const int REP = 16;
const size_t len = REP * 256;
char* data = (char*)malloc(len);
for (int i = 0; i < 256; ++i) {
for (int j = 0; j < REP; ++j) {
data[i * REP + j] = (char)i;
}
}
my_free_params = NULL;
ASSERT_EQ(0, b0.append_user_data(data, len, my_free));
ASSERT_EQ(1UL, b0._ref_num());
butil::IOBuf::BlockRef r = b0._front_ref();
ASSERT_EQ(1, butil::iobuf::block_shared_count(r.block));
ASSERT_EQ(len, b0.size());
{
butil::IOBuf bufs[256];
for (int i = 0; i < 256; ++i) {
ASSERT_EQ((size_t)REP, b0.cutn(&bufs[i], REP));
ASSERT_EQ(len - (i+1) * REP, b0.size());
if (i != 255) {
ASSERT_EQ(1UL, b0._ref_num());
butil::IOBuf::BlockRef r = b0._front_ref();
ASSERT_EQ(i + 2, butil::iobuf::block_shared_count(r.block));
} else {
ASSERT_EQ(0UL, b0._ref_num());
ASSERT_TRUE(b0.empty());
}
}
ASSERT_EQ(NULL, my_free_params);
for (int i = 0; i < 256; ++i) {
std::string out = bufs[i].to_string();
ASSERT_EQ((size_t)REP, out.size());
for (int j = 0; j < REP; ++j) {
ASSERT_EQ((char)i, out[j]);
}
}
}
ASSERT_EQ(data, my_free_params);
}
TEST_F(IOBufTest, append_user_data_with_meta) {
butil::IOBuf b0;
const int REP = 16;
const size_t len = 256;
char* data[REP];
for (int i = 0; i < REP; ++i) {
data[i] = (char*)malloc(len);
ASSERT_EQ(0, b0.append_user_data_with_meta(data[i], len, my_free, i));
}
for (int i = 0; i < REP; ++i) {
ASSERT_EQ(i, b0.get_first_data_meta());
butil::IOBuf out;
ASSERT_EQ(len / 2, b0.cutn(&out, len / 2));
ASSERT_EQ(i, b0.get_first_data_meta());
ASSERT_EQ(len / 2, b0.cutn(&out, len / 2));
}
}
TEST_F(IOBufTest, share_tls_block) {
butil::iobuf::remove_tls_block_chain();
butil::IOBuf::Block* b = butil::iobuf::acquire_tls_block();
ASSERT_EQ(0u, butil::iobuf::block_size(b));
butil::IOBuf::Block* b2 = butil::iobuf::share_tls_block();
butil::IOBuf buf;
for (size_t i = 0; i < butil::iobuf::block_cap(b2); i++) {
buf.push_back('x');
}
// after pushing to b2, b2 is full but it is still head of tls block.
ASSERT_NE(b, b2);
butil::iobuf::release_tls_block_chain(b);
ASSERT_EQ(b, butil::iobuf::share_tls_block());
// After releasing b, now tls block is b(not full) -> b2(full) -> NULL
for (size_t i = 0; i < butil::iobuf::block_cap(b); i++) {
buf.push_back('x');
}
// now tls block is b(full) -> b2(full) -> NULL
butil::IOBuf::Block* head_block = butil::iobuf::share_tls_block();
ASSERT_EQ(0u, butil::iobuf::block_size(head_block));
ASSERT_NE(b, head_block);
ASSERT_NE(b2, head_block);
}
TEST_F(IOBufTest, acquire_tls_block) {
butil::iobuf::remove_tls_block_chain();
butil::IOBuf::Block* b = butil::iobuf::acquire_tls_block();
const size_t block_cap = butil::iobuf::block_cap(b);
butil::IOBuf buf;
for (size_t i = 0; i < block_cap; i++) {
buf.append("x");
}
ASSERT_EQ(1, butil::iobuf::get_tls_block_count());
butil::IOBuf::Block* head = butil::iobuf::get_tls_block_head();
ASSERT_EQ(butil::iobuf::block_cap(head), butil::iobuf::block_size(head));
butil::iobuf::release_tls_block_chain(b);
ASSERT_EQ(2, butil::iobuf::get_tls_block_count());
for (size_t i = 0; i < block_cap; i++) {
buf.append("x");
}
ASSERT_EQ(2, butil::iobuf::get_tls_block_count());
head = butil::iobuf::get_tls_block_head();
ASSERT_EQ(butil::iobuf::block_cap(head), butil::iobuf::block_size(head));
b = butil::iobuf::acquire_tls_block();
ASSERT_EQ(0, butil::iobuf::get_tls_block_count());
ASSERT_NE(butil::iobuf::block_cap(b), butil::iobuf::block_size(b));
}
} // namespace