summaryrefslogtreecommitdiffstats
path: root/src/test/encoding
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/test/encoding.cc543
-rw-r--r--src/test/encoding/CMakeLists.txt3
-rwxr-xr-xsrc/test/encoding/check-generated.sh101
-rwxr-xr-xsrc/test/encoding/generate-corpus-objects.sh59
-rwxr-xr-xsrc/test/encoding/identity.sh32
-rwxr-xr-xsrc/test/encoding/import-generated.sh30
-rwxr-xr-xsrc/test/encoding/import.sh23
-rwxr-xr-xsrc/test/encoding/readable.sh241
8 files changed, 1032 insertions, 0 deletions
diff --git a/src/test/encoding.cc b/src/test/encoding.cc
new file mode 100644
index 000000000..6bc89491f
--- /dev/null
+++ b/src/test/encoding.cc
@@ -0,0 +1,543 @@
+#include "include/buffer.h"
+#include "include/encoding.h"
+
+#include <fmt/format.h>
+#include "gtest/gtest.h"
+
+using namespace std;
+
+template < typename T >
+static void test_encode_and_decode(const T& src)
+{
+ bufferlist bl(1000000);
+ encode(src, bl);
+ T dst;
+ auto i = bl.cbegin();
+ decode(dst, i);
+ ASSERT_EQ(src, dst) << "Encoding roundtrip changed the string: orig=" << src << ", but new=" << dst;
+}
+
+TEST(EncodingRoundTrip, StringSimple) {
+ string my_str("I am the very model of a modern major general");
+ test_encode_and_decode < std::string >(my_str);
+}
+
+TEST(EncodingRoundTrip, StringEmpty) {
+ string my_str("");
+ test_encode_and_decode < std::string >(my_str);
+}
+
+TEST(EncodingRoundTrip, StringNewline) {
+ string my_str("foo bar baz\n");
+ test_encode_and_decode < std::string >(my_str);
+}
+
+template <typename Size, typename T>
+static void test_encode_and_nohead_nohead(Size len, const T& src)
+{
+ bufferlist bl(1000000);
+ encode(len, bl);
+ encode_nohead(src, bl);
+ T dst;
+ auto i = bl.cbegin();
+ decode(len, i);
+ decode_nohead(len, dst, i);
+ ASSERT_EQ(src, dst) << "Encoding roundtrip changed the string: orig=" << src << ", but new=" << dst;
+}
+
+TEST(EncodingRoundTrip, StringNoHead) {
+ const string str("The quick brown fox jumps over the lazy dog");
+ auto size = str.size();
+ test_encode_and_nohead_nohead(static_cast<int>(size), str);
+ test_encode_and_nohead_nohead(static_cast<unsigned>(size), str);
+ test_encode_and_nohead_nohead(static_cast<uint32_t>(size), str);
+ test_encode_and_nohead_nohead(static_cast<__u32>(size), str);
+ test_encode_and_nohead_nohead(static_cast<size_t>(size), str);
+}
+
+TEST(EncodingRoundTrip, BufferListNoHead) {
+ bufferlist bl;
+ bl.append("is this a dagger which i see before me?");
+ auto size = bl.length();
+ test_encode_and_nohead_nohead(static_cast<int>(size), bl);
+ test_encode_and_nohead_nohead(static_cast<unsigned>(size), bl);
+ test_encode_and_nohead_nohead(static_cast<uint32_t>(size), bl);
+ test_encode_and_nohead_nohead(static_cast<__u32>(size), bl);
+ test_encode_and_nohead_nohead(static_cast<size_t>(size), bl);
+}
+
+typedef std::multimap < int, std::string > multimap_t;
+typedef multimap_t::value_type my_val_ty;
+
+namespace std {
+static std::ostream& operator<<(std::ostream& oss, const multimap_t &multimap)
+{
+ for (multimap_t::const_iterator m = multimap.begin();
+ m != multimap.end();
+ ++m)
+ {
+ oss << m->first << "->" << m->second << " ";
+ }
+ return oss;
+}
+}
+
+TEST(EncodingRoundTrip, Multimap) {
+ multimap_t multimap;
+ multimap.insert( my_val_ty(1, "foo") );
+ multimap.insert( my_val_ty(2, "bar") );
+ multimap.insert( my_val_ty(2, "baz") );
+ multimap.insert( my_val_ty(3, "lucky number 3") );
+ multimap.insert( my_val_ty(10000, "large number") );
+
+ test_encode_and_decode < multimap_t >(multimap);
+}
+
+
+
+///////////////////////////////////////////////////////
+// ConstructorCounter
+///////////////////////////////////////////////////////
+template <typename T>
+class ConstructorCounter
+{
+public:
+ ConstructorCounter() : data(0)
+ {
+ default_ctor++;
+ }
+
+ explicit ConstructorCounter(const T& data_)
+ : data(data_)
+ {
+ one_arg_ctor++;
+ }
+
+ ConstructorCounter(const ConstructorCounter &rhs)
+ : data(rhs.data)
+ {
+ copy_ctor++;
+ }
+
+ ConstructorCounter &operator=(const ConstructorCounter &rhs)
+ {
+ data = rhs.data;
+ assigns++;
+ return *this;
+ }
+
+ static void init(void)
+ {
+ default_ctor = 0;
+ one_arg_ctor = 0;
+ copy_ctor = 0;
+ assigns = 0;
+ }
+
+ static int get_default_ctor(void)
+ {
+ return default_ctor;
+ }
+
+ static int get_one_arg_ctor(void)
+ {
+ return one_arg_ctor;
+ }
+
+ static int get_copy_ctor(void)
+ {
+ return copy_ctor;
+ }
+
+ static int get_assigns(void)
+ {
+ return assigns;
+ }
+
+ bool operator<(const ConstructorCounter &rhs) const
+ {
+ return data < rhs.data;
+ }
+
+ bool operator==(const ConstructorCounter &rhs) const
+ {
+ return data == rhs.data;
+ }
+
+ friend void decode(ConstructorCounter &s, bufferlist::const_iterator& p)
+ {
+ decode(s.data, p);
+ }
+
+ friend void encode(const ConstructorCounter &s, bufferlist& p)
+ {
+ encode(s.data, p);
+ }
+
+ friend ostream& operator<<(ostream &oss, const ConstructorCounter &cc)
+ {
+ oss << cc.data;
+ return oss;
+ }
+
+ T data;
+private:
+ static int default_ctor;
+ static int one_arg_ctor;
+ static int copy_ctor;
+ static int assigns;
+};
+
+template class ConstructorCounter <int32_t>;
+template class ConstructorCounter <int16_t>;
+
+typedef ConstructorCounter <int32_t> my_key_t;
+typedef ConstructorCounter <int16_t> my_val_t;
+typedef std::multimap < my_key_t, my_val_t > multimap2_t;
+typedef multimap2_t::value_type val2_ty;
+
+template <class T> int ConstructorCounter<T>::default_ctor = 0;
+template <class T> int ConstructorCounter<T>::one_arg_ctor = 0;
+template <class T> int ConstructorCounter<T>::copy_ctor = 0;
+template <class T> int ConstructorCounter<T>::assigns = 0;
+
+static std::ostream& operator<<(std::ostream& oss, const multimap2_t &multimap)
+{
+ for (multimap2_t::const_iterator m = multimap.begin();
+ m != multimap.end();
+ ++m)
+ {
+ oss << m->first << "->" << m->second << " ";
+ }
+ return oss;
+}
+
+TEST(EncodingRoundTrip, MultimapConstructorCounter) {
+ multimap2_t multimap2;
+ multimap2.insert( val2_ty(my_key_t(1), my_val_t(10)) );
+ multimap2.insert( val2_ty(my_key_t(2), my_val_t(20)) );
+ multimap2.insert( val2_ty(my_key_t(2), my_val_t(30)) );
+ multimap2.insert( val2_ty(my_key_t(3), my_val_t(40)) );
+ multimap2.insert( val2_ty(my_key_t(10000), my_val_t(1)) );
+
+ my_key_t::init();
+ my_val_t::init();
+ test_encode_and_decode < multimap2_t >(multimap2);
+
+ EXPECT_EQ(my_key_t::get_default_ctor(), 5);
+ EXPECT_EQ(my_key_t::get_one_arg_ctor(), 0);
+ EXPECT_EQ(my_key_t::get_copy_ctor(), 5);
+ EXPECT_EQ(my_key_t::get_assigns(), 0);
+
+ EXPECT_EQ(my_val_t::get_default_ctor(), 5);
+ EXPECT_EQ(my_val_t::get_one_arg_ctor(), 0);
+ EXPECT_EQ(my_val_t::get_copy_ctor(), 5);
+ EXPECT_EQ(my_val_t::get_assigns(), 0);
+}
+
+namespace ceph {
+// make sure that the legacy encode/decode methods are selected
+// over the ones defined using templates. the later is likely to
+// be slower, see also the definition of "WRITE_INT_DENC" in
+// include/denc.h
+template<>
+void encode<uint64_t, denc_traits<uint64_t>>(const uint64_t&,
+ bufferlist&,
+ uint64_t f) {
+ static_assert(denc_traits<uint64_t>::supported,
+ "should support new encoder");
+ static_assert(!denc_traits<uint64_t>::featured,
+ "should not be featured");
+ ASSERT_EQ(0UL, f);
+ // make sure the test fails if i get called
+ ASSERT_TRUE(false);
+}
+
+template<>
+void encode<ceph_le64, denc_traits<ceph_le64>>(const ceph_le64&,
+ bufferlist&,
+ uint64_t f) {
+ static_assert(denc_traits<ceph_le64>::supported,
+ "should support new encoder");
+ static_assert(!denc_traits<ceph_le64>::featured,
+ "should not be featured");
+ ASSERT_EQ(0UL, f);
+ // make sure the test fails if i get called
+ ASSERT_TRUE(false);
+}
+}
+
+namespace {
+ // search `underlying_type` in denc.h for supported underlying types
+ enum class Colour : int8_t { R,G,B };
+ ostream& operator<<(ostream& os, Colour c) {
+ switch (c) {
+ case Colour::R:
+ return os << "Colour::R";
+ case Colour::G:
+ return os << "Colour::G";
+ case Colour::B:
+ return os << "Colour::B";
+ default:
+ return os << "Colour::???";
+ }
+ }
+}
+
+TEST(EncodingRoundTrip, Integers) {
+ // int types
+ {
+ uint64_t i = 42;
+ test_encode_and_decode(i);
+ }
+ {
+ int16_t i = 42;
+ test_encode_and_decode(i);
+ }
+ {
+ bool b = true;
+ test_encode_and_decode(b);
+ }
+ {
+ bool b = false;
+ test_encode_and_decode(b);
+ }
+ // raw encoder
+ {
+ ceph_le64 i;
+ i = 42;
+ test_encode_and_decode(i);
+ }
+ // enum
+ {
+ test_encode_and_decode(Colour::R);
+ // this should not build, as the size of unsigned is not the same on
+ // different archs, that's why denc_traits<> intentionally leaves
+ // `int` and `unsigned int` out of supported types.
+ //
+ // enum E { R, G, B };
+ // test_encode_and_decode(R);
+ }
+}
+
+TEST(EncodingException, Macros) {
+ const struct {
+ buffer::malformed_input exc;
+ std::string expected_what;
+ } tests[] = {
+ {
+ DECODE_ERR_OLDVERSION(__PRETTY_FUNCTION__, 100, 200),
+ fmt::format("{} no longer understand old encoding version 100 < 200: Malformed input",
+ __PRETTY_FUNCTION__)
+ },
+ {
+ DECODE_ERR_PAST(__PRETTY_FUNCTION__),
+ fmt::format("{} decode past end of struct encoding: Malformed input",
+ __PRETTY_FUNCTION__)
+ }
+ };
+ for (auto& [exec, expected_what] : tests) {
+ try {
+ throw exec;
+ } catch (const exception& e) {
+ ASSERT_NE(string(e.what()).find(expected_what), string::npos);
+ }
+ }
+}
+
+
+TEST(small_encoding, varint) {
+ uint32_t v[][4] = {
+ /* value, varint bytes, signed varint bytes, signed varint bytes (neg) */
+ {0, 1, 1, 1},
+ {1, 1, 1, 1},
+ {2, 1, 1, 1},
+ {31, 1, 1, 1},
+ {32, 1, 1, 1},
+ {0xff, 2, 2, 2},
+ {0x100, 2, 2, 2},
+ {0xfff, 2, 2, 2},
+ {0x1000, 2, 2, 2},
+ {0x2000, 2, 3, 3},
+ {0x3fff, 2, 3, 3},
+ {0x4000, 3, 3, 3},
+ {0x4001, 3, 3, 3},
+ {0x10001, 3, 3, 3},
+ {0x20001, 3, 3, 3},
+ {0x40001, 3, 3, 3},
+ {0x80001, 3, 3, 3},
+ {0x7f0001, 4, 4, 4},
+ {0xff00001, 4, 5, 5},
+ {0x1ff00001, 5, 5, 5},
+ {0xffff0001, 5, 5, 5},
+ {0xffffffff, 5, 5, 5},
+ {1074790401, 5, 5, 5},
+ {0, 0, 0, 0}
+ };
+ for (unsigned i=0; v[i][1]; ++i) {
+ {
+ bufferlist bl;
+ {
+ auto app = bl.get_contiguous_appender(16, true);
+ denc_varint(v[i][0], app);
+ }
+ cout << std::hex << v[i][0] << "\t" << v[i][1] << "\t";
+ bl.hexdump(cout, false);
+ cout << std::endl;
+ ASSERT_EQ(bl.length(), v[i][1]);
+ uint32_t u;
+ auto p = bl.begin().get_current_ptr().cbegin();
+ denc_varint(u, p);
+ ASSERT_EQ(v[i][0], u);
+ }
+ {
+ bufferlist bl;
+ {
+ auto app = bl.get_contiguous_appender(16, true);
+ denc_signed_varint(v[i][0], app);
+ }
+ cout << std::hex << v[i][0] << "\t" << v[i][2] << "\t";
+ bl.hexdump(cout, false);
+ cout << std::endl;
+ ASSERT_EQ(bl.length(), v[i][2]);
+ int32_t u;
+ auto p = bl.begin().get_current_ptr().cbegin();
+ denc_signed_varint(u, p);
+ ASSERT_EQ((int32_t)v[i][0], u);
+ }
+ {
+ bufferlist bl;
+ int64_t x = -(int64_t)v[i][0];
+ {
+ auto app = bl.get_contiguous_appender(16, true);
+ denc_signed_varint(x, app);
+ }
+ cout << std::dec << x << std::hex << "\t" << v[i][3] << "\t";
+ bl.hexdump(cout, false);
+ cout << std::endl;
+ ASSERT_EQ(bl.length(), v[i][3]);
+ int64_t u;
+ auto p = bl.begin().get_current_ptr().cbegin();
+ denc_signed_varint(u, p);
+ ASSERT_EQ(x, u);
+ }
+ }
+}
+
+TEST(small_encoding, varint_lowz) {
+ uint32_t v[][4] = {
+ /* value, bytes encoded */
+ {0, 1, 1, 1},
+ {1, 1, 1, 1},
+ {2, 1, 1, 1},
+ {15, 1, 1, 1},
+ {16, 1, 1, 1},
+ {31, 1, 2, 2},
+ {63, 2, 2, 2},
+ {64, 1, 1, 1},
+ {0xff, 2, 2, 2},
+ {0x100, 1, 1, 1},
+ {0x7ff, 2, 2, 2},
+ {0xfff, 2, 3, 3},
+ {0x1000, 1, 1, 1},
+ {0x4000, 1, 1, 1},
+ {0x8000, 1, 1, 1},
+ {0x10000, 1, 2, 2},
+ {0x20000, 2, 2, 2},
+ {0x40000, 2, 2, 2},
+ {0x80000, 2, 2, 2},
+ {0x7f0000, 2, 2, 2},
+ {0xffff0000, 4, 4, 4},
+ {0xffffffff, 5, 5, 5},
+ {0x41000000, 3, 4, 4},
+ {0, 0, 0, 0}
+ };
+ for (unsigned i=0; v[i][1]; ++i) {
+ {
+ bufferlist bl;
+ {
+ auto app = bl.get_contiguous_appender(16, true);
+ denc_varint_lowz(v[i][0], app);
+ }
+ cout << std::hex << v[i][0] << "\t" << v[i][1] << "\t";
+ bl.hexdump(cout, false);
+ cout << std::endl;
+ ASSERT_EQ(bl.length(), v[i][1]);
+ uint32_t u;
+ auto p = bl.begin().get_current_ptr().cbegin();
+ denc_varint_lowz(u, p);
+ ASSERT_EQ(v[i][0], u);
+ }
+ {
+ bufferlist bl;
+ int64_t x = v[i][0];
+ {
+ auto app = bl.get_contiguous_appender(16, true);
+ denc_signed_varint_lowz(x, app);
+ }
+ cout << std::hex << x << "\t" << v[i][1] << "\t";
+ bl.hexdump(cout, false);
+ cout << std::endl;
+ ASSERT_EQ(bl.length(), v[i][2]);
+ int64_t u;
+ auto p = bl.begin().get_current_ptr().cbegin();
+ denc_signed_varint_lowz(u, p);
+ ASSERT_EQ(x, u);
+ }
+ {
+ bufferlist bl;
+ int64_t x = -(int64_t)v[i][0];
+ {
+ auto app = bl.get_contiguous_appender(16, true);
+ denc_signed_varint_lowz(x, app);
+ }
+ cout << std::dec << x << "\t" << v[i][1] << "\t";
+ bl.hexdump(cout, false);
+ cout << std::endl;
+ ASSERT_EQ(bl.length(), v[i][3]);
+ int64_t u;
+ auto p = bl.begin().get_current_ptr().cbegin();
+ denc_signed_varint_lowz(u, p);
+ ASSERT_EQ(x, u);
+ }
+ }
+}
+
+TEST(small_encoding, lba) {
+ uint64_t v[][2] = {
+ /* value, bytes encoded */
+ {0, 4},
+ {1, 4},
+ {0xff, 4},
+ {0x10000, 4},
+ {0x7f0000, 4},
+ {0xffff0000, 4},
+ {0x0fffffff, 4},
+ {0x1fffffff, 5},
+ {0xffffffff, 5},
+ {0x3fffffff000, 4},
+ {0x7fffffff000, 5},
+ {0x1fffffff0000, 4},
+ {0x3fffffff0000, 5},
+ {0xfffffff00000, 4},
+ {0x1fffffff00000, 5},
+ {0x41000000, 4},
+ {0, 0}
+ };
+ for (unsigned i=0; v[i][1]; ++i) {
+ bufferlist bl;
+ {
+ auto app = bl.get_contiguous_appender(16, true);
+ denc_lba(v[i][0], app);
+ }
+ cout << std::hex << v[i][0] << "\t" << v[i][1] << "\t";
+ bl.hexdump(cout, false);
+ cout << std::endl;
+ ASSERT_EQ(bl.length(), v[i][1]);
+ uint64_t u;
+ auto p = bl.begin().get_current_ptr().cbegin();
+ denc_lba(u, p);
+ ASSERT_EQ(v[i][0], u);
+ }
+
+}
diff --git a/src/test/encoding/CMakeLists.txt b/src/test/encoding/CMakeLists.txt
new file mode 100644
index 000000000..e8c57a042
--- /dev/null
+++ b/src/test/encoding/CMakeLists.txt
@@ -0,0 +1,3 @@
+# scripts
+add_ceph_test(check-generated.sh ${CMAKE_CURRENT_SOURCE_DIR}/check-generated.sh)
+add_ceph_test(readable.sh ${CMAKE_CURRENT_SOURCE_DIR}/readable.sh)
diff --git a/src/test/encoding/check-generated.sh b/src/test/encoding/check-generated.sh
new file mode 100755
index 000000000..2569bc1a5
--- /dev/null
+++ b/src/test/encoding/check-generated.sh
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+set -e
+
+source $(dirname $0)/../detect-build-env-vars.sh
+source $CEPH_ROOT/qa/standalone/ceph-helpers.sh
+
+dir=$1
+
+tmp1=`mktemp /tmp/typ-XXXXXXXXX`
+tmp2=`mktemp /tmp/typ-XXXXXXXXX`
+tmp3=`mktemp /tmp/typ-XXXXXXXXX`
+tmp4=`mktemp /tmp/typ-XXXXXXXXX`
+
+failed=0
+numtests=0
+echo "checking ceph-dencoder generated test instances..."
+echo "numgen type"
+while read type; do
+ num=`ceph-dencoder type $type count_tests`
+ echo "$num $type"
+ for n in `seq 1 1 $num 2>/dev/null`; do
+
+ pids=""
+ run_in_background pids save_stdout "$tmp1" ceph-dencoder type "$type" select_test "$n" dump_json
+ run_in_background pids save_stdout "$tmp2" ceph-dencoder type "$type" select_test "$n" encode decode dump_json
+ run_in_background pids save_stdout "$tmp3" ceph-dencoder type "$type" select_test "$n" copy dump_json
+ run_in_background pids save_stdout "$tmp4" ceph-dencoder type "$type" select_test "$n" copy_ctor dump_json
+ wait_background pids
+
+ if [ $? -ne 0 ]; then
+ echo "**** $type test $n encode+decode check failed ****"
+ echo " ceph-dencoder type $type select_test $n encode decode"
+ failed=$(($failed + 3))
+ continue
+ fi
+
+ # nondeterministic classes may dump nondeterministically. compare
+ # the sorted json output. this is a weaker test, but is better
+ # than nothing.
+ deterministic=0
+ if ceph-dencoder type "$type" is_deterministic; then
+ deterministic=1
+ fi
+
+ if [ $deterministic -eq 0 ]; then
+ echo " sorting json output for nondeterministic object"
+ for f in $tmp1 $tmp2 $tmp3 $tmp4; do
+ sort $f | sed 's/,$//' > $f.new
+ mv $f.new $f
+ done
+ fi
+
+ if ! cmp $tmp1 $tmp2; then
+ echo "**** $type test $n dump_json check failed ****"
+ echo " ceph-dencoder type $type select_test $n dump_json > $tmp1"
+ echo " ceph-dencoder type $type select_test $n encode decode dump_json > $tmp2"
+ diff $tmp1 $tmp2
+ failed=$(($failed + 1))
+ fi
+
+ if ! cmp $tmp1 $tmp3; then
+ echo "**** $type test $n copy dump_json check failed ****"
+ echo " ceph-dencoder type $type select_test $n dump_json > $tmp1"
+ echo " ceph-dencoder type $type select_test $n copy dump_json > $tmp2"
+ diff $tmp1 $tmp2
+ failed=$(($failed + 1))
+ fi
+
+ if ! cmp $tmp1 $tmp4; then
+ echo "**** $type test $n copy_ctor dump_json check failed ****"
+ echo " ceph-dencoder type $type select_test $n dump_json > $tmp1"
+ echo " ceph-dencoder type $type select_test $n copy_ctor dump_json > $tmp2"
+ diff $tmp1 $tmp2
+ failed=$(($failed + 1))
+ fi
+
+ if [ $deterministic -ne 0 ]; then
+ run_in_background pids ceph-dencoder type "$type" select_test $n encode export "$tmp1"
+ run_in_background pids ceph-dencoder type "$type" select_test $n encode decode encode export "$tmp2"
+ wait_background pids
+
+ if ! cmp $tmp1 $tmp2; then
+ echo "**** $type test $n binary reencode check failed ****"
+ echo " ceph-dencoder type $type select_test $n encode export $tmp1"
+ echo " ceph-dencoder type $type select_test $n encode decode encode export $tmp2"
+ diff <(hexdump -C $tmp1) <(hexdump -C $tmp2)
+ failed=$(($failed + 1))
+ fi
+ fi
+
+ numtests=$(($numtests + 3))
+ done
+done < <(ceph-dencoder list_types)
+
+rm -f $tmp1 $tmp2 $tmp3 $tmp4
+
+if [ $failed -gt 0 ]; then
+ echo "FAILED $failed / $numtests tests."
+ exit 1
+fi
+echo "passed $numtests tests."
diff --git a/src/test/encoding/generate-corpus-objects.sh b/src/test/encoding/generate-corpus-objects.sh
new file mode 100755
index 000000000..559ac524d
--- /dev/null
+++ b/src/test/encoding/generate-corpus-objects.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+set -ex
+
+BDIR=`pwd`
+
+p=$1
+echo path $p
+test ! -d $p
+mkdir $p
+strings bin/ceph-osd | grep "^$p/%s__%d.%x"
+
+v=`git describe | cut -c 2-`
+echo version $v
+
+echo 'binaries look ok, vstarting'
+echo
+
+MON=3 MDS=3 OSD=5 MDS=3 MGR=2 RGW=1 ../src/vstart.sh -x -n -l --bluestore -e
+
+export PATH=bin:$PATH
+
+# do some work to generate a hopefully braod set of object instances
+
+echo 'starting some background work'
+../qa/workunits/rados/test.sh &
+../qa/workunits/rbd/test_librbd.sh &
+../qa/workunits/libcephfs/test.sh &
+../qa/workunits/rgw/run-s3tests.sh &
+ceph-syn --syn makedirs 3 3 3 &
+
+echo 'waiting a bit'
+
+sleep 10
+echo 'triggering some recovery'
+
+kill -9 `cat out/osd.0.pid`
+sleep 10
+ceph osd out 0
+sleep 10
+init-ceph start osd.0
+ceph osd in 0
+
+sleep 5
+echo 'triggering mds work'
+bin/ceph mds fail 0
+
+echo 'waiting for worker to join (and ignoring errors)'
+wait || true
+
+echo 'importing'
+../src/test/encoding/import.sh $p $v ../ceph-object-corpus/archive
+
+for d in ../ceph-object-corpus/archive/$v/objects/*
+do
+ echo prune $d
+ ../ceph-object-corpus/bin/prune.sh $d 25
+done
+
+echo 'done'
diff --git a/src/test/encoding/identity.sh b/src/test/encoding/identity.sh
new file mode 100755
index 000000000..67c803c9d
--- /dev/null
+++ b/src/test/encoding/identity.sh
@@ -0,0 +1,32 @@
+#!/bin/sh -e
+
+dir=$1
+
+set -e
+
+tmp1=`mktemp /tmp/typ-XXXXXXXXX`
+tmp2=`mktemp /tmp/typ-XXXXXXXXX`
+
+for type in `ls $dir`
+do
+ if ./ceph-dencoder type $type 2>/dev/null; then
+ echo "type $type"
+ for o in `ls $dir/$type`; do
+ f="$dir/$type/$o"
+ echo "\t$f"
+
+ ./ceph-dencoder type $type import $f decode dump_json > $tmp1
+ ./ceph-dencoder type $type import $f decode encode decode dump_json > $tmp2
+ cmp $tmp1 $tmp2 || exit 1
+
+ ./ceph-dencoder type $type import $f decode encode export $tmp1
+ cmp $tmp1 $f || exit 1
+ done
+ else
+ echo "skip $type"
+ fi
+done
+
+rm -f $tmp1 $tmp2
+
+echo OK
diff --git a/src/test/encoding/import-generated.sh b/src/test/encoding/import-generated.sh
new file mode 100755
index 000000000..c328e6fd8
--- /dev/null
+++ b/src/test/encoding/import-generated.sh
@@ -0,0 +1,30 @@
+#!/bin/sh -e
+
+archive=$1
+
+[ -d "$archive" ] || echo "usage: $0 <archive>"
+
+ver=`bin/ceph-dencoder version`
+echo "version $ver"
+
+[ -d "$archive/$ver" ] || mkdir "$archive/$ver"
+
+tmp1=`mktemp /tmp/typ-XXXXXXXXX`
+
+echo "numgen\ttype"
+for type in `bin/ceph-dencoder list_types`; do
+
+ [ -d "$archive/$ver/objects/$type" ] || mkdir -p "$archive/$ver/objects/$type"
+
+ num=`bin/ceph-dencoder type $type count_tests`
+ echo "$num\t$type"
+ max=$(($num - 1))
+ for n in `seq 0 $max`; do
+ bin/ceph-dencoder type $type select_test $n encode export $tmp1
+ md=`md5sum $tmp1 | awk '{print $1}'`
+ echo "\t$md"
+ [ -e "$archive/$ver/objects/$type/$md" ] || cp $tmp1 $archive/$ver/objects/$type/$md
+ done
+done
+
+rm $tmp1
diff --git a/src/test/encoding/import.sh b/src/test/encoding/import.sh
new file mode 100755
index 000000000..eea96e353
--- /dev/null
+++ b/src/test/encoding/import.sh
@@ -0,0 +1,23 @@
+#!/bin/sh -e
+
+src=$1
+ver=$2
+archive=$3
+
+[ -d "$archive" ] && [ -d "$src" ] || echo "usage: $0 <srcdir> <version> <archive>"
+
+[ -d "$archive/$ver" ] || mkdir "$archive/$ver"
+
+dest_dir="$archive/$ver/objects"
+
+[ -d "$dest_dir" ] || mkdir "$dest_dir"
+
+for f in `find $src -type f`
+do
+ n=`basename $f`
+ type=`echo $n | sed 's/__.*//'`
+ md=`md5sum $f | awk '{print $1}'`
+
+ [ -d "$dest_dir/$type" ] || mkdir $dest_dir/$type
+ [ -e "$dest_dir/$type/$md" ] || cp $f $dest_dir/$type/$md
+done
diff --git a/src/test/encoding/readable.sh b/src/test/encoding/readable.sh
new file mode 100755
index 000000000..81545852c
--- /dev/null
+++ b/src/test/encoding/readable.sh
@@ -0,0 +1,241 @@
+#!/usr/bin/env bash
+set -e
+
+source $(dirname $0)/../detect-build-env-vars.sh
+
+[ -z "$CEPH_ROOT" ] && CEPH_ROOT=..
+
+dir=$CEPH_ROOT/ceph-object-corpus
+
+failed=0
+numtests=0
+pids=""
+
+if [ -x ./ceph-dencoder ]; then
+ CEPH_DENCODER=./ceph-dencoder
+else
+ CEPH_DENCODER=ceph-dencoder
+fi
+
+myversion=`$CEPH_DENCODER version`
+DEBUG=0
+WAITALL_DELAY=.1
+debug() { if [ "$DEBUG" -gt 0 ]; then echo "DEBUG: $*" >&2; fi }
+
+test_object() {
+ local type=$1
+ local output_file=$2
+ local failed=0
+ local numtests=0
+
+ tmp1=`mktemp /tmp/test_object_1-XXXXXXXXX`
+ tmp2=`mktemp /tmp/test_object_2-XXXXXXXXX`
+
+ rm -f $output_file
+ if $CEPH_DENCODER type $type 2>/dev/null; then
+ #echo "type $type";
+ echo " $vdir/objects/$type"
+
+ # is there a fwd incompat change between $arversion and $version?
+ incompat=""
+ incompat_paths=""
+ sawarversion=0
+ for iv in `ls $dir/archive | sort -n`; do
+ if [ "$iv" = "$arversion" ]; then
+ sawarversion=1
+ fi
+
+ if [ $sawarversion -eq 1 ] && [ -e "$dir/archive/$iv/forward_incompat/$type" ]; then
+ incompat="$iv"
+
+ # Check if we'll be ignoring only specified objects, not whole type. If so, remember
+ # all paths for this type into variable. Assuming that this path won't contain any
+ # whitechars (implication of above for loop).
+ if [ -d "$dir/archive/$iv/forward_incompat/$type" ]; then
+ if [ -n "`ls $dir/archive/$iv/forward_incompat/$type/ | sort -n`" ]; then
+ incompat_paths="$incompat_paths $dir/archive/$iv/forward_incompat/$type"
+ else
+ echo "type $type directory empty, ignoring whole type instead of single objects"
+ fi;
+ fi
+ fi
+
+ if [ "$iv" = "$version" ]; then
+ rm -rf $tmp1 $tmp2
+ break
+ fi
+ done
+
+ if [ -n "$incompat" ]; then
+ if [ -z "$incompat_paths" ]; then
+ echo "skipping incompat $type version $arversion, changed at $incompat < code $myversion"
+ rm -rf $tmp1 $tmp2
+ return
+ else
+ # If we are ignoring not whole type, but objects that are in $incompat_path,
+ # we don't skip here, just give info.
+ echo "postponed skip one of incompact $type version $arversion, changed at $incompat < code $myversion"
+ fi;
+ fi
+
+ for f in `ls $vdir/objects/$type`; do
+
+ skip=0;
+ # Check if processed object $f of $type should be skipped (postponed skip)
+ if [ -n "$incompat_paths" ]; then
+ for i_path in $incompat_paths; do
+ # Check if $f is a symbolic link and if it's pointing to existing target
+ if [ -L "$i_path/$f" ]; then
+ echo "skipping object $f of type $type"
+ skip=1
+ break
+ fi;
+ done;
+ fi;
+
+ if [ $skip -ne 0 ]; then
+ continue
+ fi;
+
+ $CEPH_DENCODER type $type import $vdir/objects/$type/$f decode dump_json > $tmp1 &
+ pid1="$!"
+ $CEPH_DENCODER type $type import $vdir/objects/$type/$f decode encode decode dump_json > $tmp2 &
+ pid2="$!"
+ #echo "\t$vdir/$type/$f"
+ if ! wait $pid1; then
+ echo "**** failed to decode $vdir/objects/$type/$f ****"
+ failed=$(($failed + 1))
+ rm -f $tmp1 $tmp2
+ continue
+ fi
+ if ! wait $pid2; then
+ echo "**** failed to decode+encode+decode $vdir/objects/$type/$f ****"
+ failed=$(($failed + 1))
+ rm -f $tmp1 $tmp2
+ continue
+ fi
+
+ # nondeterministic classes may dump
+ # nondeterministically. compare the sorted json
+ # output. this is a weaker test, but is better than
+ # nothing.
+ if ! $CEPH_DENCODER type $type is_deterministic; then
+ echo " sorting json output for nondeterministic object"
+ for f in $tmp1 $tmp2; do
+ sort $f | sed 's/,$//' > $f.new
+ mv $f.new $f
+ done
+ fi
+
+ if ! cmp $tmp1 $tmp2; then
+ echo "**** reencode of $vdir/objects/$type/$f resulted in a different dump ****"
+ diff $tmp1 $tmp2
+ failed=$(($failed + 1))
+ fi
+ numtests=$(($numtests + 1))
+ rm -f $tmp1 $tmp2
+ done
+ else
+ echo "skipping unrecognized type $type"
+ rm -f $tmp1 $tmp2
+ fi
+
+ echo "failed=$failed" > $output_file
+ echo "numtests=$numtests" >> $output_file
+}
+
+waitall() { # PID...
+ ## Wait for children to exit and indicate whether all exited with 0 status.
+ local errors=0
+ while :; do
+ debug "Processes remaining: $*"
+ for pid in "$@"; do
+ shift
+ if kill -0 "$pid" 2>/dev/null; then
+ debug "$pid is still alive."
+ set -- "$@" "$pid"
+ elif wait "$pid"; then
+ debug "$pid exited with zero exit status."
+ else
+ debug "$pid exited with non-zero exit status."
+ errors=$(($errors + 1))
+ fi
+ done
+ [ $# -eq 0 ] && break
+ sleep ${WAITALL_DELAY:-1}
+ done
+ [ $errors -eq 0 ]
+}
+
+######
+# MAIN
+######
+
+do_join() {
+ waitall $pids
+ pids=""
+ # Reading the output of jobs to compute failed & numtests
+ # Tests are run in parallel but sum should be done sequentialy to avoid
+ # races between threads
+ while [ "$running_jobs" -ge 0 ]; do
+ if [ -f $output_file.$running_jobs ]; then
+ read_failed=$(grep "^failed=" $output_file.$running_jobs | cut -d "=" -f 2)
+ read_numtests=$(grep "^numtests=" $output_file.$running_jobs | cut -d "=" -f 2)
+ rm -f $output_file.$running_jobs
+ failed=$(($failed + $read_failed))
+ numtests=$(($numtests + $read_numtests))
+ fi
+ running_jobs=$(($running_jobs - 1))
+ done
+ running_jobs=0
+}
+
+# Using $MAX_PARALLEL_JOBS jobs if defined, unless the number of logical
+# processors
+if [ `uname` == FreeBSD -o `uname` == Darwin ]; then
+ NPROC=`sysctl -n hw.ncpu`
+ max_parallel_jobs=${MAX_PARALLEL_JOBS:-${NPROC}}
+else
+ max_parallel_jobs=${MAX_PARALLEL_JOBS:-$(nproc)}
+fi
+
+output_file=`mktemp /tmp/output_file-XXXXXXXXX`
+running_jobs=0
+
+for arversion in `ls $dir/archive | sort -n`; do
+ vdir="$dir/archive/$arversion"
+ #echo $vdir
+
+ if [ ! -d "$vdir/objects" ]; then
+ continue;
+ fi
+
+ for type in `ls $vdir/objects`; do
+ test_object $type $output_file.$running_jobs &
+ pids="$pids $!"
+ running_jobs=$(($running_jobs + 1))
+
+ # Once we spawned enough jobs, let's wait them to complete
+ # Every spawned job have almost the same execution time so
+ # it's not a big deal having them not ending at the same time
+ if [ "$running_jobs" -eq "$max_parallel_jobs" ]; then
+ do_join
+ fi
+ rm -f ${output_file}*
+ done
+done
+
+do_join
+
+if [ $failed -gt 0 ]; then
+ echo "FAILED $failed / $numtests tests."
+ exit 1
+fi
+
+if [ $numtests -eq 0 ]; then
+ echo "FAILED: no tests found to run!"
+ exit 1
+fi
+
+echo "passed $numtests tests."
+