// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph distributed storage system * * Copyright (C) 2016 Red Hat * * Author: Sage Weil * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * */ #include #include #include "global/global_init.h" #include "common/ceph_argparse.h" #include "global/global_context.h" #include "gtest/gtest.h" #include "include/denc.h" // test helpers template void test_encode_decode(T v) { bufferlist bl; ::encode(v, bl); bufferlist::iterator p = bl.begin(); T out; ::decode(out, p); ASSERT_EQ(v, out); } template void test_denc(T v) { // estimate size_t s = 0; denc(v, s); ASSERT_NE(s, 0u); // encode bufferlist bl; { auto a = bl.get_contiguous_appender(s); denc(v, a); } ASSERT_LE(bl.length(), s); // decode bl.rebuild(); T out; auto bpi = bl.front().begin(); denc(out, bpi); ASSERT_EQ(v, out); ASSERT_EQ(bpi.get_pos(), bl.c_str() + bl.length()); // test glue test_encode_decode(v); } template void test_encode_decode_featured(T v) { bufferlist bl; ::encode(v, bl, 123); bufferlist::iterator p = bl.begin(); T out; ::decode(out, p); ASSERT_EQ(v, out); } template void test_denc_featured(T v) { // estimate size_t s = 0; denc(v, s, 0); ASSERT_GT(s, 0u); // encode bufferlist bl; { auto a = bl.get_contiguous_appender(s); denc(v, a, 1); } ASSERT_LE(bl.length(), s); // decode bl.rebuild(); T out; auto bpi = bl.front().begin(); denc(out, bpi, 1); ASSERT_EQ(v, out); ASSERT_EQ(bpi.get_pos(), bl.c_str() + bl.length()); // test glue test_encode_decode_featured(v); } // hooks to count bound calls struct counts_t { int num_bound_encode = 0; int num_encode = 0; int num_decode = 0; void reset() { num_bound_encode = 0; num_encode = 0; num_decode = 0; } } counts; struct denc_counter_t { void bound_encode(size_t& p) const { ++counts.num_bound_encode; ++p; // denc.h does not like 0-length objects } void encode(buffer::list::contiguous_appender& p) const { p.append("a", 1); ++counts.num_encode; } void decode(buffer::ptr::iterator &p) { p.advance(1); ++counts.num_decode; } }; WRITE_CLASS_DENC(denc_counter_t) struct denc_counter_bounded_t { void bound_encode(size_t& p) const { ++counts.num_bound_encode; ++p; // denc.h does not like 0-length objects } void encode(buffer::list::contiguous_appender& p) const { p.append("a", 1); ++counts.num_encode; } void decode(buffer::ptr::iterator &p) { p.advance(1); ++counts.num_decode; } }; WRITE_CLASS_DENC_BOUNDED(denc_counter_bounded_t) TEST(denc, denc_counter) { denc_counter_t single, single2; { bufferlist bl; ::encode(single, bl); ::decode(single2, bl); } ASSERT_EQ(counts.num_bound_encode, 1); ASSERT_EQ(counts.num_encode, 1); ASSERT_EQ(counts.num_decode, 1); counts.reset(); } TEST(denc, simple) { test_denc((uint8_t)4); test_denc((int8_t)-5); test_denc((uint16_t)6); test_denc((int16_t)-7); test_denc((uint32_t)8); test_denc((int32_t)-9); test_denc((uint64_t)10); test_denc((int64_t)-11); } TEST(denc, string) { string a, b("hi"), c("multi\nline\n"); test_denc(a); test_denc(b); test_denc(c); } struct legacy_t { int32_t a = 1; void encode(bufferlist& bl) const { ::encode(a, bl); } void decode(bufferlist::iterator& p) { ::decode(a, p); } legacy_t() {} legacy_t(int32_t i) : a(i) {} friend bool operator<(const legacy_t& l, const legacy_t& r) { return l.a < r.a; } friend bool operator==(const legacy_t& l, const legacy_t& r) { return l.a == r.a; } }; WRITE_CLASS_ENCODER(legacy_t) template class C> void test_common_veclist(const char* c) { { cout << c << "" << std::endl; C s; s.push_back("foo"); s.push_back("bar"); s.push_back("baz"); counts.reset(); test_denc(s); } { cout << c << "" << std::endl; C s; s.push_back(1); s.push_back(2); s.push_back(3); test_denc(s); } { cout << c << "" << std::endl; C s; s.push_back(legacy_t(1)); s.push_back(legacy_t(2)); test_encode_decode(s); } } // We only care about specializing the type, all other template // parameters should have the default values. (Like first-class // functions, first-class templates do not bring their defaults.) template using default_vector = std::vector; TEST(denc, vector) { test_common_veclist("std::vector"); { counts.reset(); vector v, v2; v.resize(100); { bufferlist bl; ::encode(v, bl); ::decode(v2, bl); } ASSERT_EQ(counts.num_bound_encode, 100); ASSERT_EQ(counts.num_encode, 100); ASSERT_EQ(counts.num_decode, 100); } { counts.reset(); vector v, v2; v.resize(100); { bufferlist bl; ::encode(v, bl); ::decode(v2, bl); } ASSERT_EQ(counts.num_bound_encode, 1); ASSERT_EQ(counts.num_encode, 100); ASSERT_EQ(counts.num_decode, 100); } } template using default_list = std::list; TEST(denc, list) { test_common_veclist("std::list"); { counts.reset(); list l, l2; for (unsigned i=0; i<100; ++i) { l.emplace_back(denc_counter_bounded_t()); } { bufferlist bl; ::encode(l, bl); ::decode(l2, bl); } ASSERT_EQ(counts.num_bound_encode, 1); ASSERT_EQ(counts.num_encode, 100); ASSERT_EQ(counts.num_decode, 100); } } template class C> void test_setlike(const char* c) { { cout << c << "" << std::endl; C s; s.insert("foo"); s.insert("bar"); s.insert("baz"); test_denc(s); } { cout << c << "" << std::endl; C s; s.insert(1); s.insert(2); s.insert(3); test_denc(s); } { cout << c << "" << std::endl; C s; s.insert(legacy_t(1)); s.insert(legacy_t(2)); test_encode_decode(s); } } template using default_set = std::set; TEST(denc, set) { test_setlike("std::set"); } template using default_flat_set= boost::container::flat_set; TEST(denc, flat_set) { test_setlike("std::set"); } struct foo_t { int32_t a = 0; uint64_t b = 123; DENC(foo_t, v, p) { DENC_START(1, 1, p); ::denc(v.a, p); ::denc(v.b, p); DENC_FINISH(p); } friend bool operator==(const foo_t& l, const foo_t& r) { return l.a == r.a && l.b == r.b; } }; WRITE_CLASS_DENC_BOUNDED(foo_t) struct foo2_t { int32_t c = 0; uint64_t d = 123; DENC(foo2_t, v, p) { DENC_START(1, 1, p); ::denc(v.c, p); ::denc(v.d, p); DENC_FINISH(p); } friend bool operator==(const foo2_t& l, const foo2_t& r) { return l.c == r.c && l.d == r.d; } }; WRITE_CLASS_DENC_BOUNDED(foo2_t) struct bar_t { int32_t a = 0; uint64_t b = 123; DENC_FEATURED(bar_t, v, p, f) { ::denc(v.a, p, f); ::denc(v.b, p, f); } friend bool operator==(const bar_t& l, const bar_t& r) { return l.a == r.a && l.b == r.b; } }; WRITE_CLASS_DENC_FEATURED_BOUNDED(bar_t) TEST(denc, foo) { foo_t a; test_denc(a); bufferlist bl; ::encode(a, bl); bl.hexdump(cout); } TEST(denc, bar) { bar_t a; test_denc_featured(a); } TEST(denc, pair) { pair p; bufferlist bl; { auto a = bl.get_contiguous_appender(1000); denc(p, a); ::encode(p, bl); } pair lp; ::encode(lp, bl); } template class C> void test_common_maplike(const char* c) { { cout << c << "" << std::endl; C s; s["foo"] = foo_t(); s["bar"] = foo_t(); s["baz"] = foo_t(); test_denc(s); } { cout << c << "" << std::endl; C s; s["foo"] = bar_t(); s["bar"] = bar_t(); s["baz"] = bar_t(); test_denc_featured(s); } { cout << c << "" << std::endl; C s; s["foo"] = legacy_t(1); s["bar"] = legacy_t(2); test_encode_decode(s); } } template using default_map = std::map; TEST(denc, map) { test_common_maplike("std::map"); } template using default_flat_map = boost::container::flat_map; TEST(denc, flat_map) { test_common_maplike("boost::container::flat_map"); } TEST(denc, bufferptr_shallow_and_deep) { // shallow encode int32_t i = 1; bufferptr p1("foo", 3); bufferlist bl; { auto a = bl.get_contiguous_appender(100); denc(i, a); denc(p1, a); denc(i, a); } cout << "bl is " << bl << std::endl; bl.hexdump(cout); ASSERT_EQ(3u, bl.get_num_buffers()); bufferlist bl2 = bl; bl.rebuild(); bl2.rebuild(); // shallow decode { cout << "bl is " << bl << std::endl; bl.hexdump(cout); auto p = bl.front().begin(); bufferptr op; int32_t i; denc(i, p); denc(op, p); denc(i, p); ASSERT_EQ(3u, op.length()); ASSERT_EQ('f', op[0]); memset(bl.c_str(), 0, bl.length()); ASSERT_EQ(0, op[0]); } // deep decode { cout << "bl is " << bl2 << std::endl; bl2.hexdump(cout); auto p = bl2.front().begin_deep(); bufferptr op; int32_t i; denc(i, p); denc(op, p); denc(i, p); ASSERT_EQ('f', op[0]); memset(bl2.c_str(), 1, bl2.length()); ASSERT_EQ('f', op[0]); } } TEST(denc, array) { { cout << "std::array" << std::endl; std::array s = { "foo", "bar", "baz" }; counts.reset(); test_denc(s); } { cout << "std::array" << std::endl; std::array s = { 1UL, 2UL, 3UL }; test_denc(s); } } TEST(denc, tuple) { { cout << "std::tuple" << std::endl; std::tuple s(100ULL, 97UL); counts.reset(); test_denc(s); } { cout << "std::tuple" << std::endl; std::tuple s("foo", 97); test_denc(s); } { cout << "std::tuple>" << std::endl; std::tuple> s( "bar", std::set{uint32_t(1), uint32_t(2), uint32_t(3)}); test_denc(s); } } TEST(denc, optional) { { cout << "boost::optional" << std::endl; boost::optional s = 97, t = boost::none; counts.reset(); test_denc(s); test_denc(t); } { cout << "boost::optional" << std::endl; boost::optional s = std::string("Meow"), t = boost::none; counts.reset(); test_denc(s); test_denc(t); } { size_t s = 0; denc(boost::none, s); ASSERT_NE(s, 0u); // encode bufferlist bl; { auto a = bl.get_contiguous_appender(s); denc(boost::none, a); } ASSERT_LE(bl.length(), s); bl.rebuild(); boost::optional out = 5; auto bpi = bl.front().begin(); denc(out, bpi); ASSERT_FALSE(!!out); ASSERT_EQ(bpi.get_pos(), bl.c_str() + bl.length()); } } // unlike legacy_t, Legacy supports denc() also. struct Legacy { static unsigned n_denc; static unsigned n_decode; uint8_t value = 0; DENC(Legacy, v, p) { n_denc++; denc(v.value, p); } void decode(buffer::list::iterator& p) { n_decode++; ::decode(value, p); } static void reset() { n_denc = n_decode = 0; } static bufferlist encode_n(unsigned n, const vector& segments); }; WRITE_CLASS_DENC(Legacy) unsigned Legacy::n_denc = 0; unsigned Legacy::n_decode = 0; bufferlist Legacy::encode_n(unsigned n, const vector& segments) { vector v; for (unsigned i = 0; i < n; i++) { v.push_back(Legacy()); } bufferlist bl(n * sizeof(uint8_t)); ::encode(v, bl); bufferlist segmented; auto p = bl.begin(); auto sum = std::accumulate(segments.begin(), segments.end(), 0u); assert(sum != 0u); for (auto i : segments) { buffer::ptr seg; p.copy_deep(bl.length() * i / sum, seg); segmented.push_back(seg); } p.copy_all(segmented); return segmented; } TEST(denc, no_copy_if_segmented_and_lengthy) { static_assert(_denc::has_legacy_denc::value, "Legacy do have legacy denc"); { // use denc() which shallow_copy() if the buffer is small constexpr unsigned N_COPIES = 42; const vector segs{50, 50}; // half-half bufferlist segmented = Legacy::encode_n(N_COPIES, segs); ASSERT_GT(segmented.get_num_buffers(), 1u); ASSERT_LT(segmented.length(), CEPH_PAGE_SIZE); auto p = segmented.begin(); vector v; // denc() is shared by encode() and decode(), so reset() right before // decode() Legacy::reset(); ::decode(v, p); ASSERT_EQ(N_COPIES, v.size()); ASSERT_EQ(N_COPIES, Legacy::n_denc); ASSERT_EQ(0u, Legacy::n_decode); } { // use denc() which shallow_copy() if the buffer is not segmented and large const unsigned N_COPIES = CEPH_PAGE_SIZE * 2; const vector segs{100}; bufferlist segmented = Legacy::encode_n(N_COPIES, segs); ASSERT_EQ(segmented.get_num_buffers(), 1u); ASSERT_GT(segmented.length(), CEPH_PAGE_SIZE); auto p = segmented.begin(); vector v; Legacy::reset(); ::decode(v, p); ASSERT_EQ(N_COPIES, v.size()); ASSERT_EQ(N_COPIES, Legacy::n_denc); ASSERT_EQ(0u, Legacy::n_decode); } { // use denc() which shallow_copy() if the buffer is segmented and large, // but the total size of the chunks to be decoded is smallish. bufferlist large_bl = Legacy::encode_n(CEPH_PAGE_SIZE * 2, {50, 50}); bufferlist small_bl = Legacy::encode_n(100, {50, 50}); bufferlist segmented; segmented.append(large_bl); segmented.append(small_bl); ASSERT_GT(segmented.get_num_buffers(), 1u); ASSERT_GT(segmented.length(), CEPH_PAGE_SIZE); auto p = segmented.begin(); p.advance(large_bl.length()); ASSERT_LT(segmented.length() - p.get_off(), CEPH_PAGE_SIZE); vector v; Legacy::reset(); ::decode(v, p); ASSERT_EQ(Legacy::n_denc, 100u); ASSERT_EQ(0u, Legacy::n_decode); } { // use decode() which avoids deep copy if the buffer is segmented and large bufferlist small_bl = Legacy::encode_n(100, {50, 50}); bufferlist large_bl = Legacy::encode_n(CEPH_PAGE_SIZE * 2, {50, 50}); bufferlist segmented; segmented.append(small_bl); segmented.append(large_bl); ASSERT_GT(segmented.get_num_buffers(), 1u); ASSERT_GT(segmented.length(), CEPH_PAGE_SIZE); auto p = segmented.begin(); p.advance(small_bl.length()); ASSERT_GT(segmented.length() - p.get_off(), CEPH_PAGE_SIZE); vector v; Legacy::reset(); ::decode(v, p); ASSERT_EQ(0u, Legacy::n_denc); ASSERT_EQ(CEPH_PAGE_SIZE * 2, Legacy::n_decode); } }