1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph distributed storage system
6 * Copyright (C) 2016 Red Hat
8 * Author: Sage Weil <sage@redhat.com>
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
20 #include "global/global_init.h"
21 #include "common/ceph_argparse.h"
22 #include "global/global_context.h"
23 #include "gtest/gtest.h"
25 #include "include/denc.h"
32 void test_encode_decode(T v
) {
51 auto a
= bl
.get_contiguous_appender(s
);
54 ASSERT_LE(bl
.length(), s
);
59 auto bpi
= bl
.front().begin();
62 ASSERT_EQ(bpi
.get_pos(), bl
.c_str() + bl
.length());
65 test_encode_decode(v
);
69 void test_encode_decode_featured(T v
) {
79 void test_denc_featured(T v
) {
88 auto a
= bl
.get_contiguous_appender(s
);
91 ASSERT_LE(bl
.length(), s
);
96 auto bpi
= bl
.front().begin();
99 ASSERT_EQ(bpi
.get_pos(), bl
.c_str() + bl
.length());
102 test_encode_decode_featured(v
);
106 // hooks to count bound calls
109 int num_bound_encode
= 0;
113 num_bound_encode
= 0;
119 struct denc_counter_t
{
120 void bound_encode(size_t& p
) const {
121 ++counts
.num_bound_encode
;
122 ++p
; // denc.h does not like 0-length objects
124 void encode(buffer::list::contiguous_appender
& p
) const {
128 void decode(buffer::ptr::const_iterator
&p
) {
133 WRITE_CLASS_DENC(denc_counter_t
)
135 struct denc_counter_bounded_t
{
136 void bound_encode(size_t& p
) const {
137 ++counts
.num_bound_encode
;
138 ++p
; // denc.h does not like 0-length objects
140 void encode(buffer::list::contiguous_appender
& p
) const {
144 void decode(buffer::ptr::const_iterator
&p
) {
149 WRITE_CLASS_DENC_BOUNDED(denc_counter_bounded_t
)
151 TEST(denc
, denc_counter
)
153 denc_counter_t single
, single2
;
159 ASSERT_EQ(counts
.num_bound_encode
, 1);
160 ASSERT_EQ(counts
.num_encode
, 1);
161 ASSERT_EQ(counts
.num_decode
, 1);
167 test_denc((uint8_t)4);
168 test_denc((int8_t)-5);
169 test_denc((uint16_t)6);
170 test_denc((int16_t)-7);
171 test_denc((uint32_t)8);
172 test_denc((int32_t)-9);
173 test_denc((uint64_t)10);
174 test_denc((int64_t)-11);
179 string a
, b("hi"), c("multi\nline\n");
187 void encode(bufferlist
& bl
) const {
191 void decode(bufferlist::const_iterator
& p
) {
196 explicit legacy_t(int32_t i
) : a(i
) {}
197 friend bool operator<(const legacy_t
& l
, const legacy_t
& r
) {
200 friend bool operator==(const legacy_t
& l
, const legacy_t
& r
) {
204 WRITE_CLASS_ENCODER(legacy_t
)
206 template<template<class> class C
>
207 void test_common_veclist(const char* c
) {
209 cout
<< c
<< "<std::string>" << std::endl
;
218 cout
<< c
<< "<int32_t>" << std::endl
;
226 cout
<< c
<< "<legacy_t>" << std::endl
;
228 s
.push_back(legacy_t(1));
229 s
.push_back(legacy_t(2));
230 test_encode_decode(s
);
234 // We only care about specializing the type, all other template
235 // parameters should have the default values. (Like first-class
236 // functions, first-class templates do not bring their defaults.)
239 using default_vector
= std::vector
<T
>;
243 test_common_veclist
<default_vector
>("std::vector");
246 vector
<denc_counter_t
> v
, v2
;
253 ASSERT_EQ(counts
.num_bound_encode
, 100);
254 ASSERT_EQ(counts
.num_encode
, 100);
255 ASSERT_EQ(counts
.num_decode
, 100);
259 vector
<denc_counter_bounded_t
> v
, v2
;
266 ASSERT_EQ(counts
.num_bound_encode
, 1);
267 ASSERT_EQ(counts
.num_encode
, 100);
268 ASSERT_EQ(counts
.num_decode
, 100);
273 using default_list
= std::list
<T
>;
277 test_common_veclist
<default_list
>("std::list");
280 list
<denc_counter_bounded_t
> l
, l2
;
281 for (unsigned i
=0; i
<100; ++i
) {
282 l
.emplace_back(denc_counter_bounded_t());
289 ASSERT_EQ(counts
.num_bound_encode
, 1);
290 ASSERT_EQ(counts
.num_encode
, 100);
291 ASSERT_EQ(counts
.num_decode
, 100);
295 template<template<class> class C
>
296 void test_setlike(const char* c
) {
298 cout
<< c
<< "<std::string>" << std::endl
;
306 cout
<< c
<< "<int32_t>" << std::endl
;
314 cout
<< c
<< "<legacy_t>" << std::endl
;
316 s
.insert(legacy_t(1));
317 s
.insert(legacy_t(2));
318 test_encode_decode(s
);
323 using default_set
= std::set
<T
>;
327 test_setlike
<default_set
>("std::set");
331 using default_flat_set
= boost::container::flat_set
<T
>;
335 test_setlike
<default_flat_set
>("std::set");
349 friend bool operator==(const foo_t
& l
, const foo_t
& r
) {
350 return l
.a
== r
.a
&& l
.b
== r
.b
;
353 WRITE_CLASS_DENC_BOUNDED(foo_t
)
366 friend bool operator==(const foo2_t
& l
, const foo2_t
& r
) {
367 return l
.c
== r
.c
&& l
.d
== r
.d
;
370 WRITE_CLASS_DENC_BOUNDED(foo2_t
)
377 DENC_FEATURED(bar_t
, v
, p
, f
) {
382 friend bool operator==(const bar_t
& l
, const bar_t
& r
) {
383 return l
.a
== r
.a
&& l
.b
== r
.b
;
386 WRITE_CLASS_DENC_FEATURED_BOUNDED(bar_t
)
400 test_denc_featured(a
);
407 pair
<int32_t,std::string
> p
;
410 auto a
= bl
.get_contiguous_appender(1000);
415 pair
<int32_t,legacy_t
> lp
;
419 template<template<class, class> class C
>
420 void test_common_maplike(const char* c
) {
422 cout
<< c
<< "<std::string, foo_t>" << std::endl
;
430 cout
<< c
<< "<std::string, bar_t>" << std::endl
;
435 test_denc_featured(s
);
438 cout
<< c
<< "<std::string, legacy_t>" << std::endl
;
439 C
<std::string
, legacy_t
> s
;
440 s
["foo"] = legacy_t(1);
441 s
["bar"] = legacy_t(2);
442 test_encode_decode(s
);
446 template<typename U
, typename V
>
447 using default_map
= std::map
<U
, V
>;
451 test_common_maplike
<default_map
>("std::map");
454 template<typename U
, typename V
>
455 using default_flat_map
= boost::container::flat_map
<U
, V
>;
459 test_common_maplike
<default_flat_map
>("boost::container::flat_map");
462 TEST(denc
, bufferptr_shallow_and_deep
) {
465 bufferptr
p1("foo", 3);
468 auto a
= bl
.get_contiguous_appender(100);
473 cout
<< "bl is " << bl
<< std::endl
;
475 ASSERT_EQ(3u, bl
.get_num_buffers());
483 cout
<< "bl is " << bl
<< std::endl
;
485 auto p
= bl
.front().begin();
491 ASSERT_EQ(3u, op
.length());
492 ASSERT_EQ('f', op
[0]);
493 memset(bl
.c_str(), 0, bl
.length());
499 cout
<< "bl is " << bl2
<< std::endl
;
501 auto p
= bl2
.front().begin_deep();
507 ASSERT_EQ('f', op
[0]);
508 memset(bl2
.c_str(), 1, bl2
.length());
509 ASSERT_EQ('f', op
[0]);
516 cout
<< "std::array<std::string, 3>" << std::endl
;
517 std::array
<std::string
, 3> s
= { "foo", "bar", "baz" };
522 cout
<< "std::array<uint32_t, 3>" << std::endl
;
523 std::array
<uint32_t, 3> s
= { 1UL, 2UL, 3UL };
531 cout
<< "std::tuple<uint64_t, uint32_t>" << std::endl
;
532 std::tuple
<uint64_t, uint32_t> s(100ULL, 97UL);
537 cout
<< "std::tuple<std::string, uint3_t>" << std::endl
;
538 std::tuple
<std::string
, uint32_t> s("foo", 97);
542 cout
<< "std::tuple<std::string, std::set<uint32_t>>" << std::endl
;
543 std::tuple
<std::string
, std::set
<uint32_t>> s(
544 "bar", std::set
<uint32_t>{uint32_t(1), uint32_t(2), uint32_t(3)});
552 cout
<< "boost::optional<uint64_t>" << std::endl
;
553 boost::optional
<uint64_t> s
= 97, t
= boost::none
;
559 cout
<< "boost::optional<std::string>" << std::endl
;
560 boost::optional
<std::string
> s
= std::string("Meow"), t
= boost::none
;
567 denc(boost::none
, s
);
573 auto a
= bl
.get_contiguous_appender(s
);
574 denc(boost::none
, a
);
576 ASSERT_LE(bl
.length(), s
);
579 boost::optional
<uint32_t> out
= 5;
580 auto bpi
= bl
.front().begin();
583 ASSERT_EQ(bpi
.get_pos(), bl
.c_str() + bl
.length());
587 TEST(denc
, stdoptional
)
590 cout
<< "std::optional<uint64_t>" << std::endl
;
591 std::optional
<uint64_t> s
= 97, t
= std::nullopt
;
597 cout
<< "std::optional<std::string>" << std::endl
;
598 std::optional
<std::string
> s
= std::string("Meow"), t
= std::nullopt
;
605 denc(std::nullopt
, s
);
611 auto a
= bl
.get_contiguous_appender(s
);
612 denc(std::nullopt
, a
);
614 ASSERT_LE(bl
.length(), s
);
617 std::optional
<uint32_t> out
= 5;
618 auto bpi
= bl
.front().begin();
621 ASSERT_EQ(bpi
.get_pos(), bl
.c_str() + bl
.length());
625 // unlike legacy_t, Legacy supports denc() also.
627 static unsigned n_denc
;
628 static unsigned n_decode
;
634 void decode(buffer::list::const_iterator
& p
) {
639 static void reset() {
640 n_denc
= n_decode
= 0;
642 static bufferlist
encode_n(unsigned n
, const vector
<unsigned>& segments
);
644 WRITE_CLASS_DENC(Legacy
)
645 unsigned Legacy::n_denc
= 0;
646 unsigned Legacy::n_decode
= 0;
648 bufferlist
Legacy::encode_n(unsigned n
, const vector
<unsigned>& segments
) {
650 for (unsigned i
= 0; i
< n
; i
++) {
651 v
.push_back(Legacy());
653 bufferlist
bl(n
* sizeof(uint8_t));
656 bufferlist segmented
;
659 auto sum
= std::accumulate(segments
.begin(), segments
.end(), 0u);
660 ceph_assert(sum
!= 0u);
661 for (auto i
: segments
) {
663 p
.copy_deep(bl
.length() * i
/ sum
, seg
);
664 segmented
.push_back(seg
);
666 p
.copy_all(segmented
);
670 TEST(denc
, no_copy_if_segmented_and_lengthy
)
672 static_assert(_denc::has_legacy_denc
<Legacy
>::value
,
673 "Legacy do have legacy denc");
675 // use denc() which shallow_copy() if the buffer is small
676 constexpr unsigned N_COPIES
= 42;
677 const vector
<unsigned> segs
{50, 50}; // half-half
678 bufferlist segmented
= Legacy::encode_n(N_COPIES
, segs
);
679 ASSERT_GT(segmented
.get_num_buffers(), 1u);
680 ASSERT_LT(segmented
.length(), CEPH_PAGE_SIZE
);
681 auto p
= segmented
.cbegin();
683 // denc() is shared by encode() and decode(), so reset() right before
687 ASSERT_EQ(N_COPIES
, v
.size());
688 ASSERT_EQ(N_COPIES
, Legacy::n_denc
);
689 ASSERT_EQ(0u, Legacy::n_decode
);
692 // use denc() which shallow_copy() if the buffer is not segmented and large
693 const unsigned N_COPIES
= CEPH_PAGE_SIZE
* 2;
694 const vector
<unsigned> segs
{100};
695 bufferlist segmented
= Legacy::encode_n(N_COPIES
, segs
);
696 ASSERT_EQ(segmented
.get_num_buffers(), 1u);
697 ASSERT_GT(segmented
.length(), CEPH_PAGE_SIZE
);
698 auto p
= segmented
.cbegin();
702 ASSERT_EQ(N_COPIES
, v
.size());
703 ASSERT_EQ(N_COPIES
, Legacy::n_denc
);
704 ASSERT_EQ(0u, Legacy::n_decode
);
707 // use denc() which shallow_copy() if the buffer is segmented and large,
708 // but the total size of the chunks to be decoded is smallish.
709 bufferlist large_bl
= Legacy::encode_n(CEPH_PAGE_SIZE
* 2, {50, 50});
710 bufferlist small_bl
= Legacy::encode_n(100, {50, 50});
711 bufferlist segmented
;
712 segmented
.append(large_bl
);
713 segmented
.append(small_bl
);
714 ASSERT_GT(segmented
.get_num_buffers(), 1u);
715 ASSERT_GT(segmented
.length(), CEPH_PAGE_SIZE
);
716 auto p
= segmented
.cbegin();
717 p
+= large_bl
.length();
718 ASSERT_LT(segmented
.length() - p
.get_off(), CEPH_PAGE_SIZE
);
722 ASSERT_EQ(Legacy::n_denc
, 100u);
723 ASSERT_EQ(0u, Legacy::n_decode
);
726 // use decode() which avoids deep copy if the buffer is segmented and large
727 bufferlist small_bl
= Legacy::encode_n(100, {50, 50});
728 bufferlist large_bl
= Legacy::encode_n(CEPH_PAGE_SIZE
* 2, {50, 50});
729 bufferlist segmented
;
730 segmented
.append(small_bl
);
731 segmented
.append(large_bl
);
732 ASSERT_GT(segmented
.get_num_buffers(), 1u);
733 ASSERT_GT(segmented
.length(), CEPH_PAGE_SIZE
);
734 auto p
= segmented
.cbegin();
735 p
+= small_bl
.length();
736 ASSERT_GT(segmented
.length() - p
.get_off(), CEPH_PAGE_SIZE
);
740 ASSERT_EQ(0u, Legacy::n_denc
);
741 ASSERT_EQ(CEPH_PAGE_SIZE
* 2, Legacy::n_decode
);