From e6918187568dbd01842d8d1d2c808ce16a894239 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 21 Apr 2024 13:54:28 +0200 Subject: Adding upstream version 18.2.2. Signed-off-by: Daniel Baumann --- src/test/test_snap_mapper.cc | 952 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 952 insertions(+) create mode 100644 src/test/test_snap_mapper.cc (limited to 'src/test/test_snap_mapper.cc') diff --git a/src/test/test_snap_mapper.cc b/src/test/test_snap_mapper.cc new file mode 100644 index 000000000..e502892cc --- /dev/null +++ b/src/test/test_snap_mapper.cc @@ -0,0 +1,952 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +#include +#include +#include +#include +#include +#include + +#include "include/buffer.h" +#include "common/map_cacher.hpp" +#include "osd/osd_types_fmt.h" +#include "osd/SnapMapper.h" +#include "common/Cond.h" + +#include "gtest/gtest.h" + +using namespace std; + +template +typename T::iterator rand_choose(T &cont) { + if (std::empty(cont)) { + return std::end(cont); + } + return std::next(std::begin(cont), rand() % cont.size()); +} + +string random_string(size_t size) +{ + string name; + for (size_t j = 0; j < size; ++j) { + name.push_back('a' + (rand() % 26)); + } + return name; +} + +class PausyAsyncMap : public MapCacher::StoreDriver { + struct _Op { + virtual void operate(map *store) = 0; + virtual ~_Op() {} + }; + typedef std::shared_ptr<_Op> Op; + struct Remove : public _Op { + set to_remove; + explicit Remove(const set &to_remove) : to_remove(to_remove) {} + void operate(map *store) override { + for (set::iterator i = to_remove.begin(); + i != to_remove.end(); + ++i) { + store->erase(*i); + } + } + }; + struct Insert : public _Op { + map to_insert; + explicit Insert(const map &to_insert) : to_insert(to_insert) {} + void operate(map *store) override { + for (map::iterator i = to_insert.begin(); + i != to_insert.end(); + ++i) { + store->erase(i->first); + store->insert(*i); + } + } + }; + struct Callback : public _Op { + Context *context; + explicit Callback(Context *c) : context(c) {} + void operate(map *store) override { + context->complete(0); + } + }; +public: + class Transaction : public MapCacher::Transaction { + friend class PausyAsyncMap; + list ops; + list callbacks; + public: + void set_keys(const map &i) override { + ops.push_back(Op(new Insert(i))); + } + void remove_keys(const set &r) override { + ops.push_back(Op(new Remove(r))); + } + void add_callback(Context *c) override { + callbacks.push_back(Op(new Callback(c))); + } + }; +private: + + ceph::mutex lock = ceph::make_mutex("PausyAsyncMap"); + map store; + + class Doer : public Thread { + static const size_t MAX_SIZE = 100; + PausyAsyncMap *parent; + ceph::mutex lock = ceph::make_mutex("Doer lock"); + ceph::condition_variable cond; + int stopping; + bool paused; + list queue; + public: + explicit Doer(PausyAsyncMap *parent) : + parent(parent), stopping(0), paused(false) {} + void *entry() override { + while (1) { + list ops; + { + std::unique_lock l{lock}; + cond.wait(l, [this] { + return stopping || (!queue.empty() && !paused); + }); + if (stopping && queue.empty()) { + stopping = 2; + cond.notify_all(); + return 0; + } + ceph_assert(!queue.empty()); + ceph_assert(!paused); + ops.swap(queue); + cond.notify_all(); + } + ceph_assert(!ops.empty()); + + for (list::iterator i = ops.begin(); + i != ops.end(); + ops.erase(i++)) { + if (!(rand()%3)) + usleep(1+(rand() % 5000)); + std::lock_guard l{parent->lock}; + (*i)->operate(&(parent->store)); + } + } + } + + void pause() { + std::lock_guard l{lock}; + paused = true; + cond.notify_all(); + } + + void resume() { + std::lock_guard l{lock}; + paused = false; + cond.notify_all(); + } + + void submit(list &in) { + std::unique_lock l{lock}; + cond.wait(l, [this] { return queue.size() < MAX_SIZE;}); + queue.splice(queue.end(), in, in.begin(), in.end()); + cond.notify_all(); + } + + void stop() { + std::unique_lock l{lock}; + stopping = 1; + cond.notify_all(); + cond.wait(l, [this] { return stopping == 2; }); + cond.notify_all(); + } + } doer; + +public: + PausyAsyncMap() : doer(this) { + doer.create("doer"); + } + ~PausyAsyncMap() override { + doer.join(); + } + int get_keys( + const set &keys, + map *out) override { + std::lock_guard l{lock}; + for (set::const_iterator i = keys.begin(); + i != keys.end(); + ++i) { + map::iterator j = store.find(*i); + if (j != store.end()) + out->insert(*j); + } + return 0; + } + int get_next( + const string &key, + pair *next) override { + std::lock_guard l{lock}; + map::iterator j = store.upper_bound(key); + if (j != store.end()) { + if (next) + *next = *j; + return 0; + } else { + return -ENOENT; + } + } + int get_next_or_current( + const string &key, + pair *next_or_current) override { + std::lock_guard l{lock}; + map::iterator j = store.lower_bound(key); + if (j != store.end()) { + if (next_or_current) + *next_or_current = *j; + return 0; + } else { + return -ENOENT; + } + } + void submit(Transaction *t) { + doer.submit(t->ops); + doer.submit(t->callbacks); + } + + void flush() { + ceph::mutex lock = ceph::make_mutex("flush lock"); + ceph::condition_variable cond; + bool done = false; + + class OnFinish : public Context { + ceph::mutex *lock; + ceph::condition_variable *cond; + bool *done; + public: + OnFinish(ceph::mutex *lock, ceph::condition_variable *cond, bool *done) + : lock(lock), cond(cond), done(done) {} + void finish(int) override { + std::lock_guard l{*lock}; + *done = true; + cond->notify_all(); + } + }; + Transaction t; + t.add_callback(new OnFinish(&lock, &cond, &done)); + submit(&t); + { + std::unique_lock l{lock}; + cond.wait(l, [&] { return done; }); + } + } + + void pause() { + doer.pause(); + } + void resume() { + doer.resume(); + } + void stop() { + doer.stop(); + } + +}; + +class MapCacherTest : public ::testing::Test { +protected: + boost::scoped_ptr< PausyAsyncMap > driver; + boost::scoped_ptr > cache; + map truth; + set names; +public: + void assert_bl_eq(bufferlist &bl1, bufferlist &bl2) { + ASSERT_EQ(bl1.length(), bl2.length()); + bufferlist::iterator j = bl2.begin(); + for (bufferlist::iterator i = bl1.begin(); + !i.end(); + ++i, ++j) { + ASSERT_TRUE(!j.end()); + ASSERT_EQ(*i, *j); + } + } + void assert_bl_map_eq(map &m1, + map &m2) { + ASSERT_EQ(m1.size(), m2.size()); + map::iterator j = m2.begin(); + for (map::iterator i = m1.begin(); + i != m1.end(); + ++i, ++j) { + ASSERT_TRUE(j != m2.end()); + ASSERT_EQ(i->first, j->first); + assert_bl_eq(i->second, j->second); + } + + } + size_t random_num() { + return random() % 10; + } + size_t random_size() { + return random() % 1000; + } + void random_bl(size_t size, bufferlist *bl) { + for (size_t i = 0; i < size; ++i) { + bl->append(rand()); + } + } + void do_set() { + size_t set_size = random_num(); + map to_set; + for (size_t i = 0; i < set_size; ++i) { + bufferlist bl; + random_bl(random_size(), &bl); + string key = *rand_choose(names); + to_set.insert( + make_pair(key, bl)); + } + for (map::iterator i = to_set.begin(); + i != to_set.end(); + ++i) { + truth.erase(i->first); + truth.insert(*i); + } + { + PausyAsyncMap::Transaction t; + cache->set_keys(to_set, &t); + driver->submit(&t); + } + } + void remove() { + size_t remove_size = random_num(); + set to_remove; + for (size_t i = 0; i < remove_size ; ++i) { + to_remove.insert(*rand_choose(names)); + } + for (set::iterator i = to_remove.begin(); + i != to_remove.end(); + ++i) { + truth.erase(*i); + } + { + PausyAsyncMap::Transaction t; + cache->remove_keys(to_remove, &t); + driver->submit(&t); + } + } + void get() { + set to_get; + size_t get_size = random_num(); + for (size_t i = 0; i < get_size; ++i) { + to_get.insert(*rand_choose(names)); + } + + map got_truth; + for (set::iterator i = to_get.begin(); + i != to_get.end(); + ++i) { + map::iterator j = truth.find(*i); + if (j != truth.end()) + got_truth.insert(*j); + } + + map got; + cache->get_keys(to_get, &got); + + assert_bl_map_eq(got, got_truth); + } + + void get_next() { + string cur; + while (true) { + pair next; + int r = cache->get_next(cur, &next); + + pair next_truth; + map::iterator i = truth.upper_bound(cur); + int r_truth = (i == truth.end()) ? -ENOENT : 0; + if (i != truth.end()) + next_truth = *i; + + ASSERT_EQ(r, r_truth); + if (r == -ENOENT) + break; + + ASSERT_EQ(next.first, next_truth.first); + assert_bl_eq(next.second, next_truth.second); + cur = next.first; + } + } + void SetUp() override { + driver.reset(new PausyAsyncMap()); + cache.reset(new MapCacher::MapCacher(driver.get())); + names.clear(); + truth.clear(); + size_t names_size(random_num() + 10); + for (size_t i = 0; i < names_size; ++i) { + names.insert(random_string(1 + (random_size() % 10))); + } + } + void TearDown() override { + driver->stop(); + cache.reset(); + driver.reset(); + } + +}; + +TEST_F(MapCacherTest, Simple) +{ + driver->pause(); + map truth; + set truth_keys; + string blah("asdf"); + bufferlist bl; + encode(blah, bl); + truth[string("asdf")] = bl; + truth_keys.insert(truth.begin()->first); + { + PausyAsyncMap::Transaction t; + cache->set_keys(truth, &t); + driver->submit(&t); + cache->set_keys(truth, &t); + driver->submit(&t); + } + + map got; + cache->get_keys(truth_keys, &got); + assert_bl_map_eq(got, truth); + + driver->resume(); + sleep(1); + + got.clear(); + cache->get_keys(truth_keys, &got); + assert_bl_map_eq(got, truth); +} + +TEST_F(MapCacherTest, Random) +{ + for (size_t i = 0; i < 5000; ++i) { + if (!(i % 50)) { + std::cout << "On iteration " << i << std::endl; + } + switch (rand() % 4) { + case 0: + get(); + break; + case 1: + do_set(); + break; + case 2: + get_next(); + break; + case 3: + remove(); + break; + } + } +} + +class MapperVerifier { + PausyAsyncMap *driver; + boost::scoped_ptr< SnapMapper > mapper; + map > snap_to_hobject; + map> hobject_to_snap; + snapid_t next; + uint32_t mask; + uint32_t bits; + ceph::mutex lock = ceph::make_mutex("lock"); +public: + + MapperVerifier( + PausyAsyncMap *driver, + uint32_t mask, + uint32_t bits) + : driver(driver), + mapper(new SnapMapper(g_ceph_context, driver, mask, bits, 0, shard_id_t(1))), + mask(mask), bits(bits) {} + + hobject_t random_hobject() { + return hobject_t( + random_string(1+(rand() % 16)), + random_string(1+(rand() % 16)), + snapid_t(rand() % 1000), + (rand() & ((~0)< *snaps) { + ceph_assert(snaps); + ceph_assert(!snap_to_hobject.empty()); + for (int i = 0; i < num || snaps->empty(); ++i) { + snaps->insert(rand_choose(snap_to_hobject)->first); + } + } + + void create_snap() { + snap_to_hobject[next]; + ++next; + } + + void create_object() { + std::lock_guard l{lock}; + if (snap_to_hobject.empty()) + return; + hobject_t obj; + do { + obj = random_hobject(); + } while (hobject_to_snap.count(obj)); + + set &snaps = hobject_to_snap[obj]; + choose_random_snaps(1 + (rand() % 20), &snaps); + for (set::iterator i = snaps.begin(); + i != snaps.end(); + ++i) { + map >::iterator j = snap_to_hobject.find(*i); + ceph_assert(j != snap_to_hobject.end()); + j->second.insert(obj); + } + { + PausyAsyncMap::Transaction t; + mapper->add_oid(obj, snaps, &t); + driver->submit(&t); + } + } + + std::pair to_raw( + const std::pair &to_map) { + return mapper->to_raw(to_map); + } + + std::string to_legacy_raw_key( + const std::pair &to_map) { + return mapper->to_legacy_raw_key(to_map); + } + + template + std::string to_object_key(Args&&... args) { + return mapper->to_object_key(std::forward(args)...); + } + + std::string to_raw_key( + const std::pair &to_map) { + return mapper->to_raw_key(to_map); + } + + template + std::string make_purged_snap_key(Args&&... args) { + return mapper->make_purged_snap_key(std::forward(args)...); + } + + void trim_snap() { + std::lock_guard l{lock}; + if (snap_to_hobject.empty()) + return; + map >::iterator snap = + rand_choose(snap_to_hobject); + set hobjects = snap->second; + + vector hoids; + while (mapper->get_next_objects_to_trim( + snap->first, rand() % 5 + 1, &hoids) == 0) { + for (auto &&hoid: hoids) { + ceph_assert(!hoid.is_max()); + ceph_assert(hobjects.count(hoid)); + hobjects.erase(hoid); + + map>::iterator j = + hobject_to_snap.find(hoid); + ceph_assert(j->second.count(snap->first)); + set old_snaps(j->second); + j->second.erase(snap->first); + + { + PausyAsyncMap::Transaction t; + mapper->update_snaps( + hoid, + j->second, + &old_snaps, + &t); + driver->submit(&t); + } + if (j->second.empty()) { + hobject_to_snap.erase(j); + } + hoid = hobject_t::get_max(); + } + hoids.clear(); + } + ceph_assert(hobjects.empty()); + snap_to_hobject.erase(snap); + } + + void remove_oid() { + std::lock_guard l{lock}; + if (hobject_to_snap.empty()) + return; + map>::iterator obj = + rand_choose(hobject_to_snap); + for (set::iterator i = obj->second.begin(); + i != obj->second.end(); + ++i) { + map >::iterator j = + snap_to_hobject.find(*i); + ceph_assert(j->second.count(obj->first)); + j->second.erase(obj->first); + } + { + PausyAsyncMap::Transaction t; + mapper->remove_oid( + obj->first, + &t); + driver->submit(&t); + } + hobject_to_snap.erase(obj); + } + + void check_oid() { + std::lock_guard l{lock}; + if (hobject_to_snap.empty()) + return; + map>::iterator obj = + rand_choose(hobject_to_snap); + set snaps; + int r = mapper->get_snaps(obj->first, &snaps); + ceph_assert(r == 0); + ASSERT_EQ(snaps, obj->second); + } +}; + +class SnapMapperTest : public ::testing::Test { +protected: + boost::scoped_ptr< PausyAsyncMap > driver; + map > mappers; + uint32_t pgnum; + + void SetUp() override { + driver.reset(new PausyAsyncMap()); + pgnum = 0; + } + + void TearDown() override { + driver->stop(); + mappers.clear(); + driver.reset(); + } + + MapperVerifier &get_tester() { + //return *(mappers.begin()->second); + return *(rand_choose(mappers)->second); + } + + void init(uint32_t to_set) { + pgnum = to_set; + for (uint32_t i = 0; i < pgnum; ++i) { + pg_t pgid(i, 0); + mappers[pgid].reset( + new MapperVerifier( + driver.get(), + i, + pgid.get_split_bits(pgnum) + ) + ); + } + } + + void run() { + for (int i = 0; i < 5000; ++i) { + if (!(i % 50)) + std::cout << i << std::endl; + switch (rand() % 5) { + case 0: + get_tester().create_snap(); + break; + case 1: + get_tester().create_object(); + break; + case 2: + get_tester().trim_snap(); + break; + case 3: + get_tester().check_oid(); + break; + case 4: + get_tester().remove_oid(); + break; + } + } + } +}; + +TEST_F(SnapMapperTest, Simple) { + init(1); + get_tester().create_snap(); + get_tester().create_object(); + get_tester().trim_snap(); +} + +TEST_F(SnapMapperTest, More) { + init(1); + run(); +} + +TEST_F(SnapMapperTest, MultiPG) { + init(50); + run(); +} + +// Check to_object_key against current format to detect accidental changes in encoding +TEST_F(SnapMapperTest, CheckObjectKeyFormat) { + init(1); + // + std::vector> object_to_object_key({ + {hobject_t{"test_object", "", 20, 0x01234567, 20, ""}, + "OBJ_.1_0000000000000014.76543210.14.test%uobject.."}, + {hobject_t{"test._ob.ject", "k.ey", 20, 0x01234567, 20, ""}, + "OBJ_.1_0000000000000014.76543210.14.test%e%uob%eject.k%eey."}, + {hobject_t{"test_object", "", 20, 0x01234567, 20, "namespace"}, + "OBJ_.1_0000000000000014.76543210.14.test%uobject..namespace"}, + {hobject_t{ + "test_object", "", std::numeric_limits::max() - 20, 0x01234567, + std::numeric_limits::max() - 20, "namespace"}, + "OBJ_.1_7FFFFFFFFFFFFFEB.76543210.ffffffffffffffec.test%uobject..namespace"} + }); + + for (auto &[object, test_object_key]: object_to_object_key) { + auto object_key = get_tester().to_object_key(object); + if (object_key != test_object_key) { + std::cout << object << " should be " + << test_object_key << " is " + << get_tester().to_object_key(object) + << std::endl; + } + ASSERT_EQ(object_key, test_object_key); + } +} + + +// Check to_raw_key against current format to detect accidental changes in encoding +TEST_F(SnapMapperTest, CheckRawKeyFormat) { + init(1); + // + std::vector> object_to_raw_key({ + {hobject_t{"test_object", "", 20, 0x01234567, 20, ""}, 25, + "SNA_20_0000000000000019_.1_0000000000000014.76543210.14.test%uobject.."}, + {hobject_t{"test._ob.ject", "k.ey", 20, 0x01234567, 20, ""}, 25, + "SNA_20_0000000000000019_.1_0000000000000014.76543210.14.test%e%uob%eject.k%eey."}, + {hobject_t{"test_object", "", 20, 0x01234567, 20, "namespace"}, 25, + "SNA_20_0000000000000019_.1_0000000000000014.76543210.14.test%uobject..namespace"}, + {hobject_t{ + "test_object", "", std::numeric_limits::max() - 20, 0x01234567, + std::numeric_limits::max() - 20, "namespace"}, std::numeric_limits::max() - 20, + "SNA_9223372036854775787_FFFFFFFFFFFFFFEC_.1_7FFFFFFFFFFFFFEB.76543210.ffffffffffffffec.test%uobject..namespace"} + }); + + for (auto &[object, snap, test_raw_key]: object_to_raw_key) { + auto raw_key = get_tester().to_raw_key(std::make_pair(snap, object)); + if (raw_key != test_raw_key) { + std::cout << object << " " << snap << " should be " + << test_raw_key << " is " + << get_tester().to_raw_key(std::make_pair(snap, object)) + << std::endl; + } + ASSERT_EQ(raw_key, test_raw_key); + } +} + +// Check make_purged_snap_key against current format to detect accidental changes +// in encoding +TEST_F(SnapMapperTest, CheckMakePurgedSnapKeyFormat) { + init(1); + // + std::vector> purged_snap_to_key({ + {20, 30, "PSN__20_000000000000001e"}, + {std::numeric_limits::max() - 20, + std::numeric_limits::max() - 20, + "PSN__9223372036854775787_ffffffffffffffec"} + }); + + for (auto &[pool, snap, test_key]: purged_snap_to_key) { + auto raw_purged_snap_key = get_tester().make_purged_snap_key(pool, snap); + if (raw_purged_snap_key != test_key) { + std::cout << "<" << pool << ", " << snap << "> should be " << test_key + << " is " << raw_purged_snap_key << std::endl; + } + // retesting (mostly for test numbers accounting) + ASSERT_EQ(raw_purged_snap_key, test_key); + } +} + +TEST_F(SnapMapperTest, LegacyKeyConvertion) { + init(1); + auto obj = get_tester().random_hobject(); + snapid_t snapid = random() % 10; + auto snap_obj = make_pair(snapid, obj); + auto raw = get_tester().to_raw(snap_obj); + std::string old_key = get_tester().to_legacy_raw_key(snap_obj); + std::string converted_key = + SnapMapper::convert_legacy_key(old_key, raw.second); + std::string new_key = get_tester().to_raw_key(snap_obj); + if (converted_key != new_key) { + std::cout << "Converted: " << old_key << "\nTo: " << converted_key + << "\nNew key: " << new_key << std::endl; + } + ASSERT_EQ(converted_key, new_key); +} + +/** + * 'DirectMapper' provides simple, controlled, interface to the underlying + * SnapMapper. + */ +class DirectMapper { +public: + std::unique_ptr driver{make_unique()}; + std::unique_ptr mapper; + uint32_t mask; + uint32_t bits; + ceph::mutex lock = ceph::make_mutex("lock"); + + DirectMapper( + uint32_t mask, + uint32_t bits) + : mapper(new SnapMapper(g_ceph_context, driver.get(), mask, bits, 0, shard_id_t(1))), + mask(mask), bits(bits) {} + + hobject_t random_hobject() { + return hobject_t( + random_string(1+(rand() % 16)), + random_string(1+(rand() % 16)), + snapid_t(rand() % 1000), + (rand() & ((~0)< &snaps) { + std::lock_guard l{lock}; + PausyAsyncMap::Transaction t; + mapper->add_oid(obj, snaps, &t); + driver->submit(&t); + } + + std::pair to_raw( + const std::pair &to_map) { + return mapper->to_raw(to_map); + } + + std::string to_legacy_raw_key( + const std::pair &to_map) { + return mapper->to_legacy_raw_key(to_map); + } + + std::string to_raw_key( + const std::pair &to_map) { + return mapper->to_raw_key(to_map); + } + + void shorten_mapping_key(snapid_t snap, const hobject_t &clone) + { + // calculate the relevant key + std::string k = mapper->to_raw_key(snap, clone); + + // find the value for this key + map kvmap; + auto r = mapper->backend.get_keys(set{k}, &kvmap); + ASSERT_GE(r, 0); + + // replace the key with its shortened version + PausyAsyncMap::Transaction t; + mapper->backend.remove_keys(set{k}, &t); + auto short_k = k.substr(0, 10); + mapper->backend.set_keys(map{{short_k, kvmap[k]}}, &t); + driver->submit(&t); + driver->flush(); + } +}; + +class DirectMapperTest : public ::testing::Test { + public: + // ctor & initialization + DirectMapperTest() = default; + ~DirectMapperTest() = default; + void SetUp() override; + void TearDown() override; + + protected: + std::unique_ptr direct; +}; + +void DirectMapperTest::SetUp() +{ + direct = std::make_unique(0, 0); +} + +void DirectMapperTest::TearDown() +{ + direct->driver->stop(); + direct->mapper.reset(); + direct->driver.reset(); +} + + +TEST_F(DirectMapperTest, BasciObject) +{ + auto obj = direct->random_hobject(); + set snaps{100, 200}; + direct->create_object(obj, snaps); + + // verify that the OBJ_ & SNA_ entries are there + auto osn1 = direct->mapper->get_snaps(obj); + ASSERT_EQ(snaps, osn1); + auto vsn1 = direct->mapper->get_snaps_check_consistency(obj); + ASSERT_EQ(snaps, vsn1); +} + +TEST_F(DirectMapperTest, CorruptedSnaRecord) +{ + object_t base_name{"obj"}; + std::string key{"key"}; + + hobject_t head{base_name, key, CEPH_NOSNAP, 0x17, 0, ""}; + hobject_t cln1{base_name, key, 10, 0x17, 0, ""}; + hobject_t cln2{base_name, key, 20, 0x17, 0, ""}; // the oldest version + set head_snaps{400, 500}; + set cln1_snaps{300}; + set cln2_snaps{100, 200}; + + PausyAsyncMap::Transaction t; + direct->mapper->add_oid(head, head_snaps, &t); + direct->mapper->add_oid(cln1, cln1_snaps, &t); + direct->mapper->add_oid(cln2, cln2_snaps, &t); + direct->driver->submit(&t); + direct->driver->flush(); + + // verify that the OBJ_ & SNA_ entries are there + { + auto osn1 = direct->mapper->get_snaps(cln1); + EXPECT_EQ(cln1_snaps, osn1); + auto osn2 = direct->mapper->get_snaps(cln2); + EXPECT_EQ(cln2_snaps, osn2); + auto osnh = direct->mapper->get_snaps(head); + EXPECT_EQ(head_snaps, osnh); + } + { + auto vsn1 = direct->mapper->get_snaps_check_consistency(cln1); + EXPECT_EQ(cln1_snaps, vsn1); + auto vsn2 = direct->mapper->get_snaps_check_consistency(cln2); + EXPECT_EQ(cln2_snaps, vsn2); + auto vsnh = direct->mapper->get_snaps_check_consistency(head); + EXPECT_EQ(head_snaps, vsnh); + } + + // corrupt the SNA_ entry for cln1 + direct->shorten_mapping_key(300, cln1); + { + auto vsnh = direct->mapper->get_snaps_check_consistency(head); + EXPECT_EQ(head_snaps, vsnh); + auto vsn1 = direct->mapper->get_snaps(cln1); + EXPECT_EQ(cln1_snaps, vsn1); + auto osn1 = direct->mapper->get_snaps_check_consistency(cln1); + EXPECT_NE(cln1_snaps, osn1); + auto vsn2 = direct->mapper->get_snaps_check_consistency(cln2); + EXPECT_EQ(cln2_snaps, vsn2); + } +} + +///\todo test the case of a corrupted OBJ_ entry -- cgit v1.2.3