diff options
Diffstat (limited to '')
177 files changed, 12514 insertions, 0 deletions
diff --git a/src/test/common/CMakeLists.txt b/src/test/common/CMakeLists.txt new file mode 100644 index 000000000..1179fbdfb --- /dev/null +++ b/src/test/common/CMakeLists.txt @@ -0,0 +1,392 @@ +if(NOT WIN32) +# get_command_descriptions +# libmon not currently available on Windows. +add_executable(get_command_descriptions + get_command_descriptions.cc + $<TARGET_OBJECTS:common_texttable_obj> + ) +target_link_libraries(get_command_descriptions + mon + global + ${EXTRALIBS} + ${BLKID_LIBRARIES} + ${CMAKE_DL_LIBS} + ) +endif(NOT WIN32) + +# Though FreeBSD has blkdev support, the unittests' mocks only work in Linux +if(HAVE_BLKID AND LINUX) + # unittest_blkdev + add_executable(unittest_blkdev + test_blkdev.cc) + add_ceph_unittest(unittest_blkdev) + target_link_libraries(unittest_blkdev global ${BLKID_LIBRARIES}) +endif() + +# unittest_lockdep +if(WITH_CEPH_DEBUG_MUTEX) + add_executable(unittest_lockdep + test_lockdep.cc) + add_ceph_unittest(unittest_lockdep) + target_link_libraries(unittest_lockdep ceph-common) +endif() + +# unittest_counter +add_executable(unittest_counter + test_counter.cc) +add_ceph_unittest(unittest_counter) +target_link_libraries(unittest_counter ceph-common) + +# FreeBSD only has shims to support NUMA, no functional code. +if(LINUX) +# unittest_numa +add_executable(unittest_numa + test_numa.cc + ) +add_ceph_unittest(unittest_numa) +target_link_libraries(unittest_numa ceph-common) +endif() + +# unittest_bloom_filter +add_executable(unittest_bloom_filter + test_bloom_filter.cc + ) +add_ceph_unittest(unittest_bloom_filter) +target_link_libraries(unittest_bloom_filter ceph-common) + +# unittest_lruset +add_executable(unittest_lruset + test_lruset.cc + ) +add_ceph_unittest(unittest_lruset) +target_link_libraries(unittest_lruset) + +# unittest_histogram +add_executable(unittest_histogram + histogram.cc + ) +add_ceph_unittest(unittest_histogram) +target_link_libraries(unittest_histogram ceph-common) + +# unittest_prioritized_queue +add_executable(unittest_prioritized_queue + test_prioritized_queue.cc + ) +target_link_libraries(unittest_prioritized_queue ceph-common) +add_ceph_unittest(unittest_prioritized_queue) + +if(NOT WIN32) +# unittest_mclock_priority_queue +add_executable(unittest_mclock_priority_queue + test_mclock_priority_queue.cc + ) +add_ceph_unittest(unittest_mclock_priority_queue) +target_link_libraries(unittest_mclock_priority_queue + ceph-common + dmclock::dmclock) +endif(NOT WIN32) + +# unittest_str_map +add_executable(unittest_str_map + test_str_map.cc + ) +add_ceph_unittest(unittest_str_map) +target_link_libraries(unittest_str_map ceph-common) + +# unittest_json_formattable +add_executable(unittest_json_formattable + test_json_formattable.cc + ) +add_ceph_unittest(unittest_json_formattable ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittest_json_formattable) +# add_dependencies(unittest_json_formattable ceph-common) +target_link_libraries(unittest_json_formattable ceph-common global ${BLKID_LIBRARIES}) + +# unittest_json_formatter +add_executable(unittest_json_formatter + test_json_formatter.cc + ) +add_ceph_unittest(unittest_json_formatter ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittest_json_formatter) +# add_dependencies(unittest_json_formatter ceph-common) +target_link_libraries(unittest_json_formatter ceph-common global ${BLKID_LIBRARIES}) + +# unittest_sharedptr_registry +add_executable(unittest_sharedptr_registry + test_sharedptr_registry.cc + $<TARGET_OBJECTS:unit-main> + ) +add_ceph_unittest(unittest_sharedptr_registry) +target_link_libraries(unittest_sharedptr_registry global) + +# unittest_shared_cache +add_executable(unittest_shared_cache + test_shared_cache.cc + $<TARGET_OBJECTS:unit-main> + ) +add_ceph_unittest(unittest_shared_cache) +target_link_libraries(unittest_shared_cache global) + +# unittest_sloppy_crc_map +add_executable(unittest_sloppy_crc_map + test_sloppy_crc_map.cc + ) +add_ceph_unittest(unittest_sloppy_crc_map) +target_link_libraries(unittest_sloppy_crc_map global) + +# unittest_time +add_executable(unittest_time + test_time.cc + ${CMAKE_SOURCE_DIR}/src/common/ceph_time.cc + ) +add_ceph_unittest(unittest_time) +target_link_libraries(unittest_time ceph-common) + +# unittest_util +add_executable(unittest_util + test_util.cc + ${CMAKE_SOURCE_DIR}/src/common/util.cc + ) +add_ceph_unittest(unittest_util) +target_link_libraries(unittest_util global StdFilesystem::filesystem) + +# unittest_random +add_executable(unittest_random + test_random.cc + ) +add_ceph_unittest(unittest_random) +target_link_libraries(unittest_random Boost::random) + +# unittest_throttle +add_executable(unittest_throttle + Throttle.cc + $<TARGET_OBJECTS:unit-main> + ) +add_ceph_unittest(unittest_throttle PARALLEL) +target_link_libraries(unittest_throttle global) + +# unittest_lru +add_executable(unittest_lru + test_lru.cc + ) +add_ceph_unittest(unittest_lru) +target_link_libraries(unittest_lru ceph-common) + +# unittest_intrusive_lru +add_executable(unittest_intrusive_lru + test_intrusive_lru.cc + ) +add_ceph_unittest(unittest_intrusive_lru) +target_link_libraries(unittest_intrusive_lru ceph-common) + +# unittest_crc32c +add_executable(unittest_crc32c + test_crc32c.cc + ) +add_ceph_unittest(unittest_crc32c) +target_link_libraries(unittest_crc32c ceph-common) + +# unittest_config +add_executable(unittest_config + test_config.cc + test_hostname.cc + ) +add_ceph_unittest(unittest_config) +target_link_libraries(unittest_config ceph-common) + +# unittest_context +add_executable(unittest_context + test_context.cc + ) +add_ceph_unittest(unittest_context) +target_link_libraries(unittest_context ceph-common) + +# unittest_safe_io +add_executable(unittest_safe_io + test_safe_io.cc + ) +add_ceph_unittest(unittest_safe_io) +target_link_libraries(unittest_safe_io ceph-common) + +# unittest_url_escape +add_executable(unittest_url_escape + test_url_escape.cc + ) +add_ceph_unittest(unittest_url_escape) +target_link_libraries(unittest_url_escape ceph-common) + +# unittest_pretty_binary +add_executable(unittest_pretty_binary + test_pretty_binary.cc + ) +add_ceph_unittest(unittest_pretty_binary) +target_link_libraries(unittest_pretty_binary ceph-common) + +# unittest_readahead +add_executable(unittest_readahead + Readahead.cc + ) +add_ceph_unittest(unittest_readahead) +target_link_libraries(unittest_readahead ceph-common) + +# unittest_tableformatter +add_executable(unittest_tableformatter + test_tableformatter.cc + ) +add_ceph_unittest(unittest_tableformatter) +target_link_libraries(unittest_tableformatter ceph-common) + +add_executable(unittest_xmlformatter + test_xmlformatter.cc + ) +add_ceph_unittest(unittest_xmlformatter) +target_link_libraries(unittest_xmlformatter ceph-common) + +# unittest_bit_vector +add_executable(unittest_bit_vector + test_bit_vector.cc + ) +add_ceph_unittest(unittest_bit_vector) +target_link_libraries(unittest_bit_vector ceph-common) + +# unittest_interval_map +add_executable(unittest_interval_map + test_interval_map.cc +) +add_ceph_unittest(unittest_interval_map) +target_link_libraries(unittest_interval_map ceph-common) + +# unittest_interval_set +add_executable(unittest_interval_set + test_interval_set.cc +) +add_ceph_unittest(unittest_interval_set) +target_link_libraries(unittest_interval_set ceph-common GTest::Main) + +# unittest_weighted_priority_queue +add_executable(unittest_weighted_priority_queue + test_weighted_priority_queue.cc + ) +target_link_libraries(unittest_weighted_priority_queue ceph-common) +add_ceph_unittest(unittest_weighted_priority_queue) + +if(WITH_CEPH_DEBUG_MUTEX) + add_executable(unittest_mutex_debug + test_mutex_debug.cc) + add_ceph_unittest(unittest_mutex_debug) + target_link_libraries(unittest_mutex_debug ceph-common) +endif() + +# unittest_shunique_lock +add_executable(unittest_shunique_lock + test_shunique_lock.cc + ) +add_ceph_unittest(unittest_shunique_lock) +target_link_libraries(unittest_shunique_lock ceph-common) + +add_executable(unittest_fair_mutex + test_fair_mutex.cc) +add_ceph_unittest(unittest_fair_mutex) +target_link_libraries(unittest_fair_mutex ceph-common) + +# unittest_perf_histogram +add_executable(unittest_perf_histogram + test_perf_histogram.cc + ) +add_ceph_unittest(unittest_perf_histogram) +target_link_libraries(unittest_perf_histogram ceph-common) + +# unittest_perf_cache_key +add_executable(unittest_perf_counters_key test_perf_counters_key.cc) +add_ceph_unittest(unittest_perf_counters_key) +target_link_libraries(unittest_perf_counters_key ceph-common) + +# unittest_global_doublefree +if(WITH_CEPHFS) + add_executable(unittest_global_doublefree + test_global_doublefree.cc + ) + add_ceph_unittest(unittest_global_doublefree) + target_link_libraries(unittest_global_doublefree cephfs librados ceph-common) +endif(WITH_CEPHFS) + +if(NOT WIN32) +add_executable(unittest_dns_resolve + dns_resolve.cc + $<TARGET_OBJECTS:unit-main>) +target_link_libraries(unittest_dns_resolve global) +add_ceph_unittest(unittest_dns_resolve) +endif() + +add_executable(unittest_back_trace + test_back_trace.cc) +set_source_files_properties(test_back_trace.cc PROPERTIES + COMPILE_FLAGS -fno-inline) +add_ceph_unittest(unittest_back_trace) +target_link_libraries(unittest_back_trace ceph-common) + +add_executable(unittest_hostname + test_hostname.cc) +add_ceph_unittest(unittest_hostname) +target_link_libraries(unittest_hostname ceph-common) + +add_executable(unittest_iso_8601 + test_iso_8601.cc) +add_ceph_unittest(unittest_iso_8601) +target_link_libraries(unittest_iso_8601 ceph-common) + +add_executable(unittest_convenience test_convenience.cc) +add_ceph_unittest(unittest_convenience) + +add_executable(unittest_bounded_key_counter + test_bounded_key_counter.cc + $<TARGET_OBJECTS:unit-main>) +target_link_libraries(unittest_bounded_key_counter global) +add_ceph_unittest(unittest_bounded_key_counter) + +add_executable(unittest_split test_split.cc) +add_ceph_unittest(unittest_split) + +add_executable(unittest_static_ptr test_static_ptr.cc) +add_ceph_unittest(unittest_static_ptr) + +add_executable(unittest_hobject test_hobject.cc + $<TARGET_OBJECTS:unit-main>) +target_link_libraries(unittest_hobject global ceph-common) +add_ceph_unittest(unittest_hobject) + +add_executable(unittest_async_completion test_async_completion.cc) +add_ceph_unittest(unittest_async_completion) +target_link_libraries(unittest_async_completion ceph-common Boost::system) + +add_executable(unittest_async_shared_mutex test_async_shared_mutex.cc) +add_ceph_unittest(unittest_async_shared_mutex) +target_link_libraries(unittest_async_shared_mutex ceph-common Boost::system) + +add_executable(unittest_cdc test_cdc.cc + $<TARGET_OBJECTS:unit-main>) +target_link_libraries(unittest_cdc global ceph-common) +add_ceph_unittest(unittest_cdc) + +add_executable(unittest_ceph_timer test_ceph_timer.cc) +add_ceph_unittest(unittest_ceph_timer) + +add_executable(unittest_option test_option.cc) +target_link_libraries(unittest_option ceph-common GTest::Main) +add_ceph_unittest(unittest_option) + +add_executable(unittest_fault_injector test_fault_injector.cc + $<TARGET_OBJECTS:unit-main>) +target_link_libraries(unittest_fault_injector global) +add_ceph_unittest(unittest_fault_injector) + +add_executable(unittest_blocked_completion test_blocked_completion.cc) +add_ceph_unittest(unittest_blocked_completion) +target_link_libraries(unittest_blocked_completion Boost::system GTest::GTest) + +add_executable(unittest_allocate_unique test_allocate_unique.cc) +add_ceph_unittest(unittest_allocate_unique) + +if(WITH_SYSTEMD) + add_executable(unittest_journald_logger test_journald_logger.cc) + target_link_libraries(unittest_journald_logger ceph-common) + add_ceph_unittest(unittest_journald_logger) +endif() diff --git a/src/test/common/ObjectContents.cc b/src/test/common/ObjectContents.cc new file mode 100644 index 000000000..381c59c7c --- /dev/null +++ b/src/test/common/ObjectContents.cc @@ -0,0 +1,128 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +#include "ObjectContents.h" +#include "include/buffer.h" +#include <iostream> +#include <map> + +bool test_object_contents() +{ + ObjectContents c, d; + ceph_assert(!c.exists()); + c.debug(std::cerr); + c.write(10, 10, 10); + ceph_assert(c.exists()); + ceph_assert(c.size() == 20); + + c.debug(std::cerr); + bufferlist bl; + for (ObjectContents::Iterator iter = c.get_iterator(); + iter.valid(); + ++iter) { + bl.append(*iter); + } + ceph_assert(bl.length() == 20); + + bufferlist bl2; + for (unsigned i = 0; i < 8; ++i) bl2.append(bl[i]); + c.write(10, 8, 4); + c.debug(std::cerr); + ObjectContents::Iterator iter = c.get_iterator(); + iter.seek_to(8); + for (uint64_t i = 8; + i < 12; + ++i, ++iter) { + bl2.append(*iter); + } + for (unsigned i = 12; i < 20; ++i) bl2.append(bl[i]); + ceph_assert(bl2.length() == 20); + + for (ObjectContents::Iterator iter3 = c.get_iterator(); + iter.valid(); + ++iter) { + ceph_assert(bl2[iter3.get_pos()] == *iter3); + } + + ceph_assert(bl2[0] == '\0'); + ceph_assert(bl2[7] == '\0'); + + interval_set<uint64_t> to_clone; + to_clone.insert(5, 10); + d.clone_range(c, to_clone); + ceph_assert(d.size() == 15); + + c.debug(std::cerr); + d.debug(std::cerr); + + ObjectContents::Iterator iter2 = d.get_iterator(); + iter2.seek_to(5); + for (uint64_t i = 5; i < 15; ++i, ++iter2) { + std::cerr << "i is " << i << std::endl; + ceph_assert(iter2.get_pos() == i); + ceph_assert(*iter2 == bl2[i]); + } + return true; +} + + +unsigned int ObjectContents::Iterator::get_state(uint64_t _pos) +{ + if (parent->seeds.count(_pos)) { + return parent->seeds[_pos]; + } + seek_to(_pos - 1); + return current_state; +} + +void ObjectContents::clone_range(ObjectContents &other, + interval_set<uint64_t> &intervals) +{ + interval_set<uint64_t> written_to_clone; + written_to_clone.intersection_of(intervals, other.written); + + interval_set<uint64_t> zeroed = intervals; + zeroed.subtract(written_to_clone); + + written.union_of(intervals); + written.subtract(zeroed); + + for (interval_set<uint64_t>::iterator i = written_to_clone.begin(); + i != written_to_clone.end(); + ++i) { + uint64_t start = i.get_start(); + uint64_t len = i.get_len(); + + unsigned int seed = get_iterator().get_state(start+len); + + seeds[start+len] = seed; + seeds.erase(seeds.lower_bound(start), seeds.lower_bound(start+len)); + + seeds[start] = other.get_iterator().get_state(start); + seeds.insert(other.seeds.upper_bound(start), + other.seeds.lower_bound(start+len)); + } + + if (intervals.range_end() > _size) + _size = intervals.range_end(); + _exists = true; + return; +} + +void ObjectContents::write(unsigned int seed, + uint64_t start, + uint64_t len) +{ + _exists = true; + unsigned int _seed = get_iterator().get_state(start+len); + seeds[start+len] = _seed; + seeds.erase(seeds.lower_bound(start), + seeds.lower_bound(start+len)); + seeds[start] = seed; + + interval_set<uint64_t> to_write; + to_write.insert(start, len); + written.union_of(to_write); + + if (start + len > _size) + _size = start + len; + return; +} diff --git a/src/test/common/ObjectContents.h b/src/test/common/ObjectContents.h new file mode 100644 index 000000000..7834bfedf --- /dev/null +++ b/src/test/common/ObjectContents.h @@ -0,0 +1,122 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +#include "include/interval_set.h" +#include "include/buffer_fwd.h" +#include <map> + +#ifndef COMMON_OBJECT_H +#define COMMON_OBJECT_H + +enum { + RANDOMWRITEFULL, + DELETED, + CLONERANGE +}; + +bool test_object_contents(); + +class ObjectContents { + uint64_t _size; + std::map<uint64_t, unsigned int> seeds; + interval_set<uint64_t> written; + bool _exists; +public: + class Iterator { + ObjectContents *parent; + std::map<uint64_t, unsigned int>::iterator iter; + unsigned int current_state; + int current_val; + uint64_t pos; + private: + unsigned int get_state(uint64_t pos); + public: + explicit Iterator(ObjectContents *parent) : + parent(parent), iter(parent->seeds.end()), + current_state(0), current_val(0), pos(-1) { + seek_to_first(); + } + char operator*() { + return parent->written.contains(pos) ? + static_cast<char>(current_val % 256) : '\0'; + } + uint64_t get_pos() { + return pos; + } + void seek_to(uint64_t _pos) { + if (pos > _pos || + (iter != parent->seeds.end() && _pos >= iter->first)) { + iter = parent->seeds.upper_bound(_pos); + --iter; + current_state = iter->second; + current_val = rand_r(¤t_state); + pos = iter->first; + ++iter; + } + while (pos < _pos) ++(*this); + } + + void seek_to_first() { + seek_to(0); + } + Iterator &operator++() { + ++pos; + if (iter != parent->seeds.end() && pos >= iter->first) { + ceph_assert(pos == iter->first); + current_state = iter->second; + ++iter; + } + current_val = rand_r(¤t_state); + return *this; + } + bool valid() { + return pos < parent->size(); + } + friend class ObjectContents; + }; + + ObjectContents() : _size(0), _exists(false) { + seeds[0] = 0; + } + + explicit ObjectContents(bufferlist::const_iterator &bp) { + decode(_size, bp); + decode(seeds, bp); + decode(written, bp); + decode(_exists, bp); + } + + void clone_range(ObjectContents &other, + interval_set<uint64_t> &intervals); + void write(unsigned int seed, + uint64_t from, + uint64_t len); + Iterator get_iterator() { + return Iterator(this); + } + + uint64_t size() const { return _size; } + + bool exists() { return _exists; } + + void debug(std::ostream &out) { + out << "_size is " << _size << std::endl; + out << "seeds is: ("; + for (std::map<uint64_t, unsigned int>::iterator i = seeds.begin(); + i != seeds.end(); + ++i) { + out << "[" << i->first << "," << i->second << "], "; + } + out << ")" << std::endl; + out << "written is " << written << std::endl; + out << "_exists is " << _exists << std::endl; + } + + void encode(bufferlist &bl) const { + using ceph::encode; + encode(_size, bl); + encode(seeds, bl); + encode(written, bl); + encode(_exists, bl); + } +}; + +#endif diff --git a/src/test/common/Readahead.cc b/src/test/common/Readahead.cc new file mode 100644 index 000000000..30402b022 --- /dev/null +++ b/src/test/common/Readahead.cc @@ -0,0 +1,132 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2014 Adam Crume <adamcrume@gmail.com> + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "common/Readahead.h" +#include "gtest/gtest.h" +#include <stdint.h> +#include <boost/foreach.hpp> +#include <cstdarg> + + +#define ASSERT_RA(expected_offset, expected_length, ra) \ + do { \ + Readahead::extent_t e = ra; \ + ASSERT_EQ((uint64_t)expected_length, e.second); \ + if (expected_length) { \ + ASSERT_EQ((uint64_t)expected_offset, e.first); \ + } \ + } while(0) + +using namespace std; + +TEST(Readahead, random_access) { + Readahead r; + r.set_trigger_requests(2); + ASSERT_RA(0, 0, r.update(1000, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1010, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1030, 20, r.update(1020, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1040, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1060, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1080, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1100, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1200, 10, Readahead::NO_LIMIT)); +} + +TEST(Readahead, min_size_limit) { + Readahead r; + r.set_trigger_requests(2); + r.set_min_readahead_size(40); + ASSERT_RA(0, 0, r.update(1000, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1010, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1030, 40, r.update(1020, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1030, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1070, 80, r.update(1040, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1050, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1060, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1070, 10, Readahead::NO_LIMIT)); +} + +TEST(Readahead, max_size_limit) { + Readahead r; + r.set_trigger_requests(2); + r.set_max_readahead_size(50); + ASSERT_RA(0, 0, r.update(1000, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1010, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1030, 20, r.update(1020, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1050, 40, r.update(1030, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1040, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1050, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1090, 50, r.update(1060, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1070, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1080, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1090, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1100, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1140, 50, r.update(1110, 10, Readahead::NO_LIMIT)); +} + +TEST(Readahead, limit) { + Readahead r; + r.set_trigger_requests(2); + r.set_max_readahead_size(50); + uint64_t limit = 1100; + ASSERT_RA(0, 0, r.update(1000, 10, limit)); + ASSERT_RA(0, 0, r.update(1010, 10, limit)); + ASSERT_RA(1030, 20, r.update(1020, 10, limit)); + ASSERT_RA(1050, 40, r.update(1030, 10, limit)); + ASSERT_RA(0, 0, r.update(1040, 10, limit)); + ASSERT_RA(0, 0, r.update(1050, 10, limit)); + ASSERT_RA(1090, 10, r.update(1060, 10, limit)); + ASSERT_RA(0, 0, r.update(1070, 10, limit)); + ASSERT_RA(0, 0, r.update(1080, 10, limit)); + ASSERT_RA(0, 0, r.update(1090, 10, limit)); +} + +TEST(Readahead, alignment) { + Readahead r; + r.set_trigger_requests(2); + vector<uint64_t> alignment; + alignment.push_back(100); + r.set_alignments(alignment); + ASSERT_RA(0, 0, r.update(1000, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1010, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1030, 20, r.update(1020, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1050, 50, r.update(1030, 10, Readahead::NO_LIMIT)); // internal readahead size 40 + ASSERT_RA(0, 0, r.update(1040, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1050, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1060, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1100, 100, r.update(1070, 10, Readahead::NO_LIMIT)); // internal readahead size 80 + ASSERT_RA(0, 0, r.update(1080, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1090, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1100, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1110, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1120, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1130, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1200, 200, r.update(1140, 10, Readahead::NO_LIMIT)); // internal readahead size 160 + ASSERT_RA(0, 0, r.update(1150, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1160, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1170, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1180, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1190, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1200, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1210, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1220, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1230, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1240, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1250, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1260, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1270, 10, Readahead::NO_LIMIT)); + ASSERT_RA(0, 0, r.update(1280, 10, Readahead::NO_LIMIT)); + ASSERT_RA(1400, 300, r.update(1290, 10, Readahead::NO_LIMIT)); // internal readahead size 320 + ASSERT_RA(0, 0, r.update(1300, 10, Readahead::NO_LIMIT)); +} diff --git a/src/test/common/Throttle.cc b/src/test/common/Throttle.cc new file mode 100644 index 000000000..b36d0a901 --- /dev/null +++ b/src/test/common/Throttle.cc @@ -0,0 +1,386 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com> + * + * Author: Loic Dachary <loic@dachary.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library Public License for more details. + * + */ + +#include <stdio.h> +#include <signal.h> + +#include <chrono> +#include <list> +#include <mutex> +#include <random> +#include <thread> + +#include "gtest/gtest.h" +#include "common/Thread.h" +#include "common/Throttle.h" +#include "common/ceph_argparse.h" + +using namespace std; + +class ThrottleTest : public ::testing::Test { +protected: + + class Thread_get : public Thread { + public: + Throttle &throttle; + int64_t count; + bool waited = false; + + Thread_get(Throttle& _throttle, int64_t _count) : + throttle(_throttle), count(_count) {} + + void *entry() override { + usleep(5); + waited = throttle.get(count); + throttle.put(count); + return nullptr; + } + }; +}; + +TEST_F(ThrottleTest, Throttle) { + int64_t throttle_max = 10; + Throttle throttle(g_ceph_context, "throttle", throttle_max); + ASSERT_EQ(throttle.get_max(), throttle_max); + ASSERT_EQ(throttle.get_current(), 0); +} + +TEST_F(ThrottleTest, take) { + int64_t throttle_max = 10; + Throttle throttle(g_ceph_context, "throttle", throttle_max); + ASSERT_EQ(throttle.take(throttle_max), throttle_max); + ASSERT_EQ(throttle.take(throttle_max), throttle_max * 2); +} + +TEST_F(ThrottleTest, get) { + int64_t throttle_max = 10; + Throttle throttle(g_ceph_context, "throttle"); + + // test increasing max from 0 to throttle_max + { + ASSERT_FALSE(throttle.get(throttle_max, throttle_max)); + ASSERT_EQ(throttle.get_max(), throttle_max); + ASSERT_EQ(throttle.put(throttle_max), 0); + } + + ASSERT_FALSE(throttle.get(5)); + ASSERT_EQ(throttle.put(5), 0); + + ASSERT_FALSE(throttle.get(throttle_max)); + ASSERT_FALSE(throttle.get_or_fail(1)); + ASSERT_FALSE(throttle.get(1, throttle_max + 1)); + ASSERT_EQ(throttle.put(throttle_max + 1), 0); + ASSERT_FALSE(throttle.get(0, throttle_max)); + ASSERT_FALSE(throttle.get(throttle_max)); + ASSERT_FALSE(throttle.get_or_fail(1)); + ASSERT_EQ(throttle.put(throttle_max), 0); + + useconds_t delay = 1; + + bool waited; + + do { + cout << "Trying (1) with delay " << delay << "us\n"; + + ASSERT_FALSE(throttle.get(throttle_max)); + ASSERT_FALSE(throttle.get_or_fail(throttle_max)); + + Thread_get t(throttle, 7); + t.create("t_throttle_1"); + usleep(delay); + ASSERT_EQ(throttle.put(throttle_max), 0); + t.join(); + + if (!(waited = t.waited)) + delay *= 2; + } while(!waited); + + delay = 1; + do { + cout << "Trying (2) with delay " << delay << "us\n"; + + ASSERT_FALSE(throttle.get(throttle_max / 2)); + ASSERT_FALSE(throttle.get_or_fail(throttle_max)); + + Thread_get t(throttle, throttle_max); + t.create("t_throttle_2"); + usleep(delay); + + Thread_get u(throttle, 1); + u.create("u_throttle_2"); + usleep(delay); + + throttle.put(throttle_max / 2); + + t.join(); + u.join(); + + if (!(waited = t.waited && u.waited)) + delay *= 2; + } while(!waited); + +} + +TEST_F(ThrottleTest, get_or_fail) { + { + Throttle throttle(g_ceph_context, "throttle"); + + ASSERT_TRUE(throttle.get_or_fail(5)); + ASSERT_TRUE(throttle.get_or_fail(5)); + } + + { + int64_t throttle_max = 10; + Throttle throttle(g_ceph_context, "throttle", throttle_max); + + ASSERT_TRUE(throttle.get_or_fail(throttle_max)); + ASSERT_EQ(throttle.put(throttle_max), 0); + + ASSERT_TRUE(throttle.get_or_fail(throttle_max * 2)); + ASSERT_FALSE(throttle.get_or_fail(1)); + ASSERT_FALSE(throttle.get_or_fail(throttle_max * 2)); + ASSERT_EQ(throttle.put(throttle_max * 2), 0); + + ASSERT_TRUE(throttle.get_or_fail(throttle_max)); + ASSERT_FALSE(throttle.get_or_fail(1)); + ASSERT_EQ(throttle.put(throttle_max), 0); + } +} + +TEST_F(ThrottleTest, wait) { + int64_t throttle_max = 10; + Throttle throttle(g_ceph_context, "throttle"); + + // test increasing max from 0 to throttle_max + { + ASSERT_FALSE(throttle.wait(throttle_max)); + ASSERT_EQ(throttle.get_max(), throttle_max); + } + + useconds_t delay = 1; + + bool waited; + + do { + cout << "Trying (3) with delay " << delay << "us\n"; + + ASSERT_FALSE(throttle.get(throttle_max / 2)); + ASSERT_FALSE(throttle.get_or_fail(throttle_max)); + + Thread_get t(throttle, throttle_max); + t.create("t_throttle_3"); + usleep(delay); + + // + // Throttle::_reset_max(int64_t m) used to contain a test + // that blocked the following statement, only if + // the argument was greater than throttle_max. + // Although a value lower than throttle_max would cover + // the same code in _reset_max, the throttle_max * 100 + // value is left here to demonstrate that the problem + // has been solved. + // + throttle.wait(throttle_max * 100); + usleep(delay); + t.join(); + ASSERT_EQ(throttle.get_current(), throttle_max / 2); + + if (!(waited = t.waited)) { + delay *= 2; + // undo the changes we made + throttle.put(throttle_max / 2); + throttle.wait(throttle_max); + } + } while(!waited); +} + +std::pair<double, std::chrono::duration<double> > test_backoff( + double low_threshhold, + double high_threshhold, + double expected_throughput, + double high_multiple, + double max_multiple, + uint64_t max, + double put_delay_per_count, + unsigned getters, + unsigned putters) +{ + std::mutex l; + std::condition_variable c; + uint64_t total = 0; + std::list<uint64_t> in_queue; + bool stop_getters = false; + bool stop_putters = false; + + auto wait_time = std::chrono::duration<double>(0); + uint64_t waits = 0; + + uint64_t total_observed_total = 0; + uint64_t total_observations = 0; + + BackoffThrottle throttle(g_ceph_context, "backoff_throttle_test", 5); + bool valid = throttle.set_params( + low_threshhold, + high_threshhold, + expected_throughput, + high_multiple, + max_multiple, + max, + 0); + ceph_assert(valid); + + auto getter = [&]() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, 10); + + std::unique_lock<std::mutex> g(l); + while (!stop_getters) { + g.unlock(); + + uint64_t to_get = dis(gen); + auto waited = throttle.get(to_get); + + g.lock(); + wait_time += waited; + waits += to_get; + total += to_get; + in_queue.push_back(to_get); + c.notify_one(); + } + }; + + auto putter = [&]() { + std::unique_lock<std::mutex> g(l); + while (!stop_putters || !in_queue.empty()) { + if (in_queue.empty()) { + c.wait(g); + continue; + } + + uint64_t c = in_queue.front(); + + total_observed_total += total; + total_observations++; + in_queue.pop_front(); + ceph_assert(total <= max); + + g.unlock(); + std::this_thread::sleep_for( + c * std::chrono::duration<double>(put_delay_per_count*putters)); + g.lock(); + + total -= c; + throttle.put(c); + } + }; + + vector<std::thread> gts(getters); + for (auto &&i: gts) i = std::thread(getter); + + vector<std::thread> pts(putters); + for (auto &&i: pts) i = std::thread(putter); + + std::this_thread::sleep_for(std::chrono::duration<double>(5)); + { + std::unique_lock<std::mutex> g(l); + stop_getters = true; + c.notify_all(); + } + for (auto &&i: gts) i.join(); + gts.clear(); + + { + std::unique_lock<std::mutex> g(l); + stop_putters = true; + c.notify_all(); + } + for (auto &&i: pts) i.join(); + pts.clear(); + + return make_pair( + ((double)total_observed_total)/((double)total_observations), + wait_time / waits); +} + +TEST(BackoffThrottle, undersaturated) +{ + auto results = test_backoff( + 0.4, + 0.6, + 1000, + 2, + 10, + 100, + 0.0001, + 3, + 6); + ASSERT_LT(results.first, 45); + ASSERT_GT(results.first, 35); + ASSERT_LT(results.second.count(), 0.0002); + ASSERT_GT(results.second.count(), 0.00005); +} + +TEST(BackoffThrottle, balanced) +{ + auto results = test_backoff( + 0.4, + 0.6, + 1000, + 2, + 10, + 100, + 0.001, + 7, + 2); + ASSERT_LT(results.first, 60); + ASSERT_GT(results.first, 40); + ASSERT_LT(results.second.count(), 0.002); + ASSERT_GT(results.second.count(), 0.0005); +} + +TEST(BackoffThrottle, oversaturated) +{ + auto results = test_backoff( + 0.4, + 0.6, + 10000000, + 2, + 10, + 100, + 0.001, + 1, + 3); + ASSERT_LT(results.first, 101); + ASSERT_GT(results.first, 85); + ASSERT_LT(results.second.count(), 0.002); + ASSERT_GT(results.second.count(), 0.0005); +} + +/* + * Local Variables: + * compile-command: "cd ../.. ; + * make unittest_throttle ; + * ./unittest_throttle # --gtest_filter=ThrottleTest.take \ + * --log-to-stderr=true --debug-filestore=20 + * " + * End: + */ diff --git a/src/test/common/blkdev-udevadm-info-samples/autriche.nvme0n1 b/src/test/common/blkdev-udevadm-info-samples/autriche.nvme0n1 new file mode 100644 index 000000000..f4fdd636e --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/autriche.nvme0n1 @@ -0,0 +1,17 @@ +P: /devices/pci0000:00/0000:00:1d.0/0000:04:00.0/nvme/nvme0/nvme0n1 +N: nvme0n1 +S: disk/by-id/nvme-Samsung_SSD_960_EVO_250GB_S3ESNX0J958081E +S: disk/by-id/nvme-eui.0025385971b11793 +E: DEVLINKS=/dev/disk/by-id/nvme-Samsung_SSD_960_EVO_250GB_S3ESNX0J958081E /dev/disk/by-id/nvme-eui.0025385971b11793 +E: DEVNAME=/dev/nvme0n1 +E: DEVPATH=/devices/pci0000:00/0000:00:1d.0/0000:04:00.0/nvme/nvme0/nvme0n1 +E: DEVTYPE=disk +E: ID_PART_TABLE_TYPE=gpt +E: ID_PART_TABLE_UUID=c83d5616-676b-4667-bcf3-c82fd4fc7e64 +E: ID_SERIAL=Samsung SSD 960 EVO 250GB_S3ESNX0J958081E +E: ID_SERIAL_SHORT=S3ESNX0J958081E +E: MAJOR=259 +E: MINOR=0 +E: SUBSYSTEM=block +E: TAGS=:systemd: +E: USEC_INITIALIZED=1875432 diff --git a/src/test/common/blkdev-udevadm-info-samples/autriche.nvme0n1.devid b/src/test/common/blkdev-udevadm-info-samples/autriche.nvme0n1.devid new file mode 100644 index 000000000..844c6a956 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/autriche.nvme0n1.devid @@ -0,0 +1 @@ +Samsung_SSD_960_EVO_250GB_S3ESNX0J958081E diff --git a/src/test/common/blkdev-udevadm-info-samples/cpach.sdn b/src/test/common/blkdev-udevadm-info-samples/cpach.sdn new file mode 100644 index 000000000..a47a5270a --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/cpach.sdn @@ -0,0 +1,53 @@ +P: /devices/pci0000:80/0000:80:01.0/0000:82:00.0/host10/port-10:0/expander-10:0/port-10:0:13/end_device-10:0:13/target10:0:13/10:0:13:0/block/sdn +N: sdn +S: disk/by-id/ata-WDC_WDS200T2B0A-00SM50_183503800168 +S: disk/by-id/lvm-pv-uuid-LUClYG-Oyte-jcM6-npfZ-ncsl-ycL0-bkOH0m +S: disk/by-id/wwn-0x5001b448b96ce4fd +S: disk/by-path/pci-0000:82:00.0-sas-exp0x50030480091072bf-phy29-lun-0 +E: DEVLINKS=/dev/disk/by-path/pci-0000:82:00.0-sas-exp0x50030480091072bf-phy29-lun-0 /dev/disk/by-id/wwn-0x5001b448b96ce4fd /dev/disk/by-id/ata-WDC_WDS200T2B0A-00SM50_183503800168 /dev/disk/by-id/lvm-pv-uuid-LUClYG-Oyte-jcM6-npfZ-ncsl-ycL0-bkOH0m +E: DEVNAME=/dev/sdn +E: DEVPATH=/devices/pci0000:80/0000:80:01.0/0000:82:00.0/host10/port-10:0/expander-10:0/port-10:0:13/end_device-10:0:13/target10:0:13/10:0:13:0/block/sdn +E: DEVTYPE=disk +E: ID_ATA=1 +E: ID_ATA_DOWNLOAD_MICROCODE=1 +E: ID_ATA_FEATURE_SET_APM=1 +E: ID_ATA_FEATURE_SET_APM_CURRENT_VALUE=254 +E: ID_ATA_FEATURE_SET_APM_ENABLED=1 +E: ID_ATA_FEATURE_SET_PM=1 +E: ID_ATA_FEATURE_SET_PM_ENABLED=1 +E: ID_ATA_FEATURE_SET_SECURITY=1 +E: ID_ATA_FEATURE_SET_SECURITY_ENABLED=0 +E: ID_ATA_FEATURE_SET_SECURITY_ENHANCED_ERASE_UNIT_MIN=2 +E: ID_ATA_FEATURE_SET_SECURITY_ERASE_UNIT_MIN=2 +E: ID_ATA_FEATURE_SET_SMART=1 +E: ID_ATA_FEATURE_SET_SMART_ENABLED=1 +E: ID_ATA_ROTATION_RATE_RPM=0 +E: ID_ATA_SATA=1 +E: ID_ATA_SATA_SIGNAL_RATE_GEN1=1 +E: ID_ATA_SATA_SIGNAL_RATE_GEN2=1 +E: ID_ATA_WRITE_CACHE=1 +E: ID_ATA_WRITE_CACHE_ENABLED=1 +E: ID_BUS=ata +E: ID_FS_TYPE=LVM2_member +E: ID_FS_USAGE=raid +E: ID_FS_UUID=LUClYG-Oyte-jcM6-npfZ-ncsl-ycL0-bkOH0m +E: ID_FS_UUID_ENC=LUClYG-Oyte-jcM6-npfZ-ncsl-ycL0-bkOH0m +E: ID_FS_VERSION=LVM2 001 +E: ID_MODEL=LVM PV LUClYG-Oyte-jcM6-npfZ-ncsl-ycL0-bkOH0m on /dev/sdn +E: ID_MODEL_ENC=WDC\x20WDS200T2B0A-00SM50\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20 +E: ID_PATH=pci-0000:82:00.0-sas-exp0x50030480091072bf-phy29-lun-0 +E: ID_PATH_TAG=pci-0000_82_00_0-sas-exp0x50030480091072bf-phy29-lun-0 +E: ID_REVISION=X61190WD +E: ID_SERIAL=WDC_WDS200T2B0A-00SM50_183503800168 +E: ID_SERIAL_SHORT=183503800168 +E: ID_TYPE=disk +E: ID_WWN=0x5001b448b96ce4fd +E: ID_WWN_WITH_EXTENSION=0x5001b448b96ce4fd +E: MAJOR=8 +E: MINOR=208 +E: SUBSYSTEM=block +E: SYSTEMD_ALIAS=/dev/block/8:208 +E: SYSTEMD_READY=1 +E: SYSTEMD_WANTS=lvm2-pvscan@8:208.service +E: TAGS=:systemd: +E: USEC_INITIALIZED=20972398 diff --git a/src/test/common/blkdev-udevadm-info-samples/cpach.sdn.devid b/src/test/common/blkdev-udevadm-info-samples/cpach.sdn.devid new file mode 100644 index 000000000..004460cf7 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/cpach.sdn.devid @@ -0,0 +1 @@ +WDC_WDS200T2B0A-00SM50_183503800168 diff --git a/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sda b/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sda new file mode 100644 index 000000000..7e82dfb41 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sda @@ -0,0 +1,31 @@ +P: /devices/pci0000:00/0000:00:01.0/0000:01:00.0/host0/target0:2:0/0:2:0:0/block/sda +N: sda +S: disk/by-id/scsi-36c81f660e62885001b3147f40c5abb67 +S: disk/by-id/wwn-0x6c81f660e62885001b3147f40c5abb67 +S: disk/by-path/pci-0000:01:00.0-scsi-0:2:0:0 +E: DEVLINKS=/dev/disk/by-id/scsi-36c81f660e62885001b3147f40c5abb67 /dev/disk/by-id/wwn-0x6c81f660e62885001b3147f40c5abb67 /dev/disk/by-path/pci-0000:01:00.0-scsi-0:2:0:0 +E: DEVNAME=/dev/sda +E: DEVPATH=/devices/pci0000:00/0000:00:01.0/0000:01:00.0/host0/target0:2:0/0:2:0:0/block/sda +E: DEVTYPE=disk +E: ID_BUS=scsi +E: ID_MODEL=PERC_H310 +E: ID_MODEL_ENC=PERC\x20H310 +E: ID_PART_TABLE_TYPE=dos +E: ID_PATH=pci-0000:01:00.0-scsi-0:2:0:0 +E: ID_PATH_TAG=pci-0000_01_00_0-scsi-0_2_0_0 +E: ID_REVISION=2.12 +E: ID_SCSI=1 +E: ID_SCSI_SERIAL=0067bb5a0cf447311b008528e660f681 +E: ID_SERIAL=36c81f660e62885001b3147f40c5abb67 +E: ID_SERIAL_SHORT=6c81f660e62885001b3147f40c5abb67 +E: ID_TYPE=disk +E: ID_VENDOR=DELL +E: ID_VENDOR_ENC=DELL +E: ID_WWN=0x6c81f660e6288500 +E: ID_WWN_VENDOR_EXTENSION=0x1b3147f40c5abb67 +E: ID_WWN_WITH_EXTENSION=0x6c81f660e62885001b3147f40c5abb67 +E: MAJOR=8 +E: MINOR=0 +E: SUBSYSTEM=block +E: TAGS=:systemd: +E: USEC_INITIALIZED=26959 diff --git a/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sda.devid b/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sda.devid new file mode 100644 index 000000000..5269648e2 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sda.devid @@ -0,0 +1 @@ +DELL_PERC_H310_0067bb5a0cf447311b008528e660f681 diff --git a/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sdb b/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sdb new file mode 100644 index 000000000..b922d3178 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sdb @@ -0,0 +1,32 @@ +P: /devices/pci0000:00/0000:00:03.0/0000:02:00.0/0000:03:00.0/0000:04:00.0/0000:05:10.0/0000:08:00.0/host7/target7:2:1/7:2:1:0/block/sdb +N: sdb +S: disk/by-id/scsi-36c81f660ee4cf7001aecfd72a34e6992 +S: disk/by-id/wwn-0x6c81f660ee4cf7001aecfd72a34e6992 +S: disk/by-path/pci-0000:08:00.0-scsi-0:2:1:0 +E: DEVLINKS=/dev/disk/by-id/scsi-36c81f660ee4cf7001aecfd72a34e6992 /dev/disk/by-id/wwn-0x6c81f660ee4cf7001aecfd72a34e6992 /dev/disk/by-path/pci-0000:08:00.0-scsi-0:2:1:0 +E: DEVNAME=/dev/sdb +E: DEVPATH=/devices/pci0000:00/0000:00:03.0/0000:02:00.0/0000:03:00.0/0000:04:00.0/0000:05:10.0/0000:08:00.0/host7/target7:2:1/7:2:1:0/block/sdb +E: DEVTYPE=disk +E: ID_BUS=scsi +E: ID_MODEL=Shared_PERC8 +E: ID_MODEL_ENC=Shared\x20PERC8\x20\x20\x20\x20 +E: ID_PART_TABLE_TYPE=gpt +E: ID_PATH=pci-0000:08:00.0-scsi-0:2:1:0 +E: ID_PATH_TAG=pci-0000_08_00_0-scsi-0_2_1_0 +E: ID_REVISION=3.24 +E: ID_SCSI=1 +E: ID_SCSI_SERIAL=6192694ea372fdec1a00f74cee60f681 +E: ID_SERIAL=36c81f660ee4cf7001aecfd72a34e6992 +E: ID_SERIAL_SHORT=6c81f660ee4cf7001aecfd72a34e6992 +E: ID_TARGET_PORT=100 +E: ID_TYPE=disk +E: ID_VENDOR=DELL +E: ID_VENDOR_ENC=DELL\x20\x20\x20\x20 +E: ID_WWN=0x6c81f660ee4cf700 +E: ID_WWN_VENDOR_EXTENSION=0x1aecfd72a34e6992 +E: ID_WWN_WITH_EXTENSION=0x6c81f660ee4cf7001aecfd72a34e6992 +E: MAJOR=8 +E: MINOR=16 +E: SUBSYSTEM=block +E: TAGS=:systemd: +E: USEC_INITIALIZED=28137 diff --git a/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sdb.devid b/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sdb.devid new file mode 100644 index 000000000..f8005c46b --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/erwan.v1.sdb.devid @@ -0,0 +1 @@ +DELL_Shared_PERC8_6192694ea372fdec1a00f74cee60f681 diff --git a/src/test/common/blkdev-udevadm-info-samples/erwan1 b/src/test/common/blkdev-udevadm-info-samples/erwan1 new file mode 100644 index 000000000..5db39ea61 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/erwan1 @@ -0,0 +1,31 @@ +P: /devices/pci0000:00/0000:00:02.2/0000:02:00.0/host0/target0:1:0/0:1:0:0/block/sda +N: sda +S: disk/by-id/scsi-3600508b1001ca3a81462043ff6d56249 +S: disk/by-id/wwn-0x600508b1001ca3a81462043ff6d56249 +S: disk/by-path/pci-0000:02:00.0-scsi-0:1:0:0 +E: DEVLINKS=/dev/disk/by-id/scsi-3600508b1001ca3a81462043ff6d56249 /dev/disk/by-id/wwn-0x600508b1001ca3a81462043ff6d56249 /dev/disk/by-path/pci-0000:02:00.0-scsi-0:1:0:0 +E: DEVNAME=/dev/sda +E: DEVPATH=/devices/pci0000:00/0000:00:02.2/0000:02:00.0/host0/target0:1:0/0:1:0:0/block/sda +E: DEVTYPE=disk +E: ID_BUS=scsi +E: ID_MODEL=LOGICAL_VOLUME +E: ID_MODEL_ENC=LOGICAL\x20VOLUME\x20\x20 +E: ID_PART_TABLE_TYPE=dos +E: ID_PATH=pci-0000:02:00.0-scsi-0:1:0:0 +E: ID_PATH_TAG=pci-0000_02_00_0-scsi-0_1_0_0 +E: ID_REVISION=8.32 +E: ID_SCSI=1 +E: ID_SCSI_SERIAL=0014380281544E0 +E: ID_SERIAL=3600508b1001ca3a81462043ff6d56249 +E: ID_SERIAL_SHORT=600508b1001ca3a81462043ff6d56249 +E: ID_TYPE=disk +E: ID_VENDOR=HP +E: ID_VENDOR_ENC=HP\x20\x20\x20\x20\x20\x20 +E: ID_WWN=0x600508b1001ca3a8 +E: ID_WWN_VENDOR_EXTENSION=0x1462043ff6d56249 +E: ID_WWN_WITH_EXTENSION=0x600508b1001ca3a81462043ff6d56249 +E: MAJOR=8 +E: MINOR=0 +E: SUBSYSTEM=block +E: TAGS=:systemd: +E: USEC_INITIALIZED=50763 diff --git a/src/test/common/blkdev-udevadm-info-samples/gnit.nvme0n1 b/src/test/common/blkdev-udevadm-info-samples/gnit.nvme0n1 new file mode 100644 index 000000000..906f544c6 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/gnit.nvme0n1 @@ -0,0 +1,25 @@ +P: /devices/pci0000:80/0000:80:03.0/0000:82:00.0/nvme/nvme0/nvme0n1 +N: nvme0n1 +S: disk/by-id/nvme-INTEL_SSDPEDMD400G4_CVFT520200G7400BGN +S: disk/by-id/nvme-nvme.8086-43564654353230323030473734303042474e-494e54454c205353445045444d443430304734-00000001 +S: disk/by-path/pci-0000:82:00.0-nvme-1 +S: disk/by-uuid/860d4503-9c9d-4c24-af09-4266b7717a5c +E: DEVLINKS=/dev/disk/by-id/nvme-nvme.8086-43564654353230323030473734303042474e-494e54454c205353445045444d443430304734-00000001 /dev/disk/by-uuid/860d4503-9c9d-4c24-af09-4266b7717a5c /dev/disk/by-path/pci-0000:82:00.0-nvme-1 /dev/disk/by-id/nvme-INTEL_SSDPEDMD400G4_CVFT520200G7400BGN +E: DEVNAME=/dev/nvme0n1 +E: DEVPATH=/devices/pci0000:80/0000:80:03.0/0000:82:00.0/nvme/nvme0/nvme0n1 +E: DEVTYPE=disk +E: ID_FS_TYPE=xfs +E: ID_FS_USAGE=filesystem +E: ID_FS_UUID=860d4503-9c9d-4c24-af09-4266b7717a5c +E: ID_FS_UUID_ENC=860d4503-9c9d-4c24-af09-4266b7717a5c +E: ID_MODEL=INTEL SSDPEDMD400G4 +E: ID_PATH=pci-0000:82:00.0-nvme-1 +E: ID_PATH_TAG=pci-0000_82_00_0-nvme-1 +E: ID_SERIAL=INTEL SSDPEDMD400G4_CVFT520200G7400BGN +E: ID_SERIAL_SHORT=CVFT520200G7400BGN +E: ID_WWN=nvme.8086-43564654353230323030473734303042474e-494e54454c205353445045444d443430304734-00000001 +E: MAJOR=259 +E: MINOR=0 +E: SUBSYSTEM=block +E: TAGS=:systemd: +E: USEC_INITIALIZED=5097198 diff --git a/src/test/common/blkdev-udevadm-info-samples/gnit.nvme0n1.devid b/src/test/common/blkdev-udevadm-info-samples/gnit.nvme0n1.devid new file mode 100644 index 000000000..9238afd19 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/gnit.nvme0n1.devid @@ -0,0 +1 @@ +INTEL_SSDPEDMD400G4_CVFT520200G7400BGN diff --git a/src/test/common/blkdev-udevadm-info-samples/gnit.sda b/src/test/common/blkdev-udevadm-info-samples/gnit.sda new file mode 100644 index 000000000..7673a6afd --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/gnit.sda @@ -0,0 +1,46 @@ +P: /devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda +N: sda +S: disk/by-id/ata-INTEL_SSDSC2BB240G4_BTWL3414034J240NGN +S: disk/by-id/wwn-0x55cd2e404b4e47d8 +S: disk/by-path/pci-0000:00:1f.2-ata-1 +E: DEVLINKS=/dev/disk/by-id/wwn-0x55cd2e404b4e47d8 /dev/disk/by-path/pci-0000:00:1f.2-ata-1 /dev/disk/by-id/ata-INTEL_SSDSC2BB240G4_BTWL3414034J240NGN +E: DEVNAME=/dev/sda +E: DEVPATH=/devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda +E: DEVTYPE=disk +E: ID_ATA=1 +E: ID_ATA_DOWNLOAD_MICROCODE=1 +E: ID_ATA_FEATURE_SET_HPA=1 +E: ID_ATA_FEATURE_SET_HPA_ENABLED=1 +E: ID_ATA_FEATURE_SET_PM=1 +E: ID_ATA_FEATURE_SET_PM_ENABLED=1 +E: ID_ATA_FEATURE_SET_SECURITY=1 +E: ID_ATA_FEATURE_SET_SECURITY_ENABLED=0 +E: ID_ATA_FEATURE_SET_SECURITY_ENHANCED_ERASE_UNIT_MIN=2 +E: ID_ATA_FEATURE_SET_SECURITY_ERASE_UNIT_MIN=2 +E: ID_ATA_FEATURE_SET_SECURITY_FROZEN=1 +E: ID_ATA_FEATURE_SET_SMART=1 +E: ID_ATA_FEATURE_SET_SMART_ENABLED=1 +E: ID_ATA_ROTATION_RATE_RPM=0 +E: ID_ATA_SATA=1 +E: ID_ATA_SATA_SIGNAL_RATE_GEN1=1 +E: ID_ATA_SATA_SIGNAL_RATE_GEN2=1 +E: ID_ATA_WRITE_CACHE=1 +E: ID_ATA_WRITE_CACHE_ENABLED=1 +E: ID_BUS=ata +E: ID_MODEL=INTEL_SSDSC2BB240G4 +E: ID_MODEL_ENC=INTEL\x20SSDSC2BB240G4\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20 +E: ID_PART_TABLE_TYPE=dos +E: ID_PART_TABLE_UUID=bb35118c +E: ID_PATH=pci-0000:00:1f.2-ata-1 +E: ID_PATH_TAG=pci-0000_00_1f_2-ata-1 +E: ID_REVISION=D2010355 +E: ID_SERIAL=INTEL_SSDSC2BB240G4_BTWL3414034J240NGN +E: ID_SERIAL_SHORT=BTWL3414034J240NGN +E: ID_TYPE=disk +E: ID_WWN=0x55cd2e404b4e47d8 +E: ID_WWN_WITH_EXTENSION=0x55cd2e404b4e47d8 +E: MAJOR=8 +E: MINOR=0 +E: SUBSYSTEM=block +E: TAGS=:systemd: +E: USEC_INITIALIZED=5064443 diff --git a/src/test/common/blkdev-udevadm-info-samples/gnit.sda.devid b/src/test/common/blkdev-udevadm-info-samples/gnit.sda.devid new file mode 100644 index 000000000..154a89d28 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/gnit.sda.devid @@ -0,0 +1 @@ +INTEL_SSDSC2BB240G4_BTWL3414034J240NGN diff --git a/src/test/common/blkdev-udevadm-info-samples/mira055.sda b/src/test/common/blkdev-udevadm-info-samples/mira055.sda new file mode 100644 index 000000000..5136db3b2 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/mira055.sda @@ -0,0 +1,28 @@ +P: /devices/pci0000:00/0000:00:03.0/0000:01:00.0/host0/target0:0:0/0:0:0:0/block/sda +N: sda +S: disk/by-id/scsi-2001b4d2050244200 +S: disk/by-path/pci-0000:01:00.0-scsi-0:0:0:0 +E: DEVLINKS=/dev/disk/by-id/scsi-2001b4d2050244200 /dev/disk/by-path/pci-0000:01:00.0-scsi-0:0:0:0 +E: DEVNAME=/dev/sda +E: DEVPATH=/devices/pci0000:00/0000:00:03.0/0000:01:00.0/host0/target0:0:0/0:0:0:0/block/sda +E: DEVTYPE=disk +E: ID_BUS=scsi +E: ID_MODEL=HUA722010CLA330 +E: ID_MODEL_ENC=HUA722010CLA330\x20 +E: ID_PART_TABLE_TYPE=dos +E: ID_PART_TABLE_UUID=0005eff9 +E: ID_PATH=pci-0000:01:00.0-scsi-0:0:0:0 +E: ID_PATH_TAG=pci-0000_01_00_0-scsi-0_0_0_0 +E: ID_REVISION=R001 +E: ID_SCSI=1 +E: ID_SCSI_SERIAL=JPW9P0N10U422D +E: ID_SERIAL=2001b4d2050244200 +E: ID_SERIAL_SHORT=001b4d2050244200 +E: ID_TYPE=disk +E: ID_VENDOR=Hitachi +E: ID_VENDOR_ENC=Hitachi\x20 +E: MAJOR=8 +E: MINOR=0 +E: SUBSYSTEM=block +E: TAGS=:systemd: +E: USEC_INITIALIZED=1207668 diff --git a/src/test/common/blkdev-udevadm-info-samples/mira055.sda.devid b/src/test/common/blkdev-udevadm-info-samples/mira055.sda.devid new file mode 100644 index 000000000..4cd2eac71 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/mira055.sda.devid @@ -0,0 +1 @@ +Hitachi_HUA722010CLA330_JPW9P0N10U422D diff --git a/src/test/common/blkdev-udevadm-info-samples/mira055.sdb b/src/test/common/blkdev-udevadm-info-samples/mira055.sdb new file mode 100644 index 000000000..38040fb04 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/mira055.sdb @@ -0,0 +1,28 @@ +P: /devices/pci0000:00/0000:00:03.0/0000:01:00.0/host0/target0:0:0/0:0:0:1/block/sdb +N: sdb +S: disk/by-id/scsi-2001b4d2058da3a00 +S: disk/by-path/pci-0000:01:00.0-scsi-0:0:0:1 +E: DEVLINKS=/dev/disk/by-id/scsi-2001b4d2058da3a00 /dev/disk/by-path/pci-0000:01:00.0-scsi-0:0:0:1 +E: DEVNAME=/dev/sdb +E: DEVPATH=/devices/pci0000:00/0000:00:03.0/0000:01:00.0/host0/target0:0:0/0:0:0:1/block/sdb +E: DEVTYPE=disk +E: ID_BUS=scsi +E: ID_MODEL=HUS724040ALA640 +E: ID_MODEL_ENC=HUS724040ALA640\x20 +E: ID_PART_TABLE_TYPE=gpt +E: ID_PART_TABLE_UUID=957b2db6-de5c-46cb-a672-243fa12d55b2 +E: ID_PATH=pci-0000:01:00.0-scsi-0:0:0:1 +E: ID_PATH_TAG=pci-0000_01_00_0-scsi-0_0_0_1 +E: ID_REVISION=R001 +E: ID_SCSI=1 +E: ID_SCSI_SERIAL=PN1334PBH5JMJS +E: ID_SERIAL=2001b4d2058da3a00 +E: ID_SERIAL_SHORT=001b4d2058da3a00 +E: ID_TYPE=disk +E: ID_VENDOR=HGST +E: ID_VENDOR_ENC=HGST\x20\x20\x20\x20 +E: MAJOR=8 +E: MINOR=16 +E: SUBSYSTEM=block +E: TAGS=:systemd: +E: USEC_INITIALIZED=1256327 diff --git a/src/test/common/blkdev-udevadm-info-samples/mira055.sdb.devid b/src/test/common/blkdev-udevadm-info-samples/mira055.sdb.devid new file mode 100644 index 000000000..8bfe4ce6f --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/mira055.sdb.devid @@ -0,0 +1 @@ +HGST_HUS724040ALA640_PN1334PBH5JMJS diff --git a/src/test/common/blkdev-udevadm-info-samples/mira055.sde b/src/test/common/blkdev-udevadm-info-samples/mira055.sde new file mode 100644 index 000000000..d7a929c64 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/mira055.sde @@ -0,0 +1,28 @@ +P: /devices/pci0000:00/0000:00:03.0/0000:01:00.0/host0/target0:0:0/0:0:0:4/block/sde +N: sde +S: disk/by-id/scsi-2001b4d20a20c660b +S: disk/by-path/pci-0000:01:00.0-scsi-0:0:0:4 +E: DEVLINKS=/dev/disk/by-path/pci-0000:01:00.0-scsi-0:0:0:4 /dev/disk/by-id/scsi-2001b4d20a20c660b +E: DEVNAME=/dev/sde +E: DEVPATH=/devices/pci0000:00/0000:00:03.0/0000:01:00.0/host0/target0:0:0/0:0:0:4/block/sde +E: DEVTYPE=disk +E: ID_BUS=scsi +E: ID_MODEL=WD40EFRX-68N32N0 +E: ID_MODEL_ENC=WD40EFRX-68N32N0 +E: ID_PART_TABLE_TYPE=gpt +E: ID_PART_TABLE_UUID=444372c2-981b-402a-9af1-2eb734d99ebe +E: ID_PATH=pci-0000:01:00.0-scsi-0:0:0:4 +E: ID_PATH_TAG=pci-0000_01_00_0-scsi-0_0_0_4 +E: ID_REVISION=R001 +E: ID_SCSI=1 +E: ID_SCSI_SERIAL=WD-WCC7K2ZL0V6K +E: ID_SERIAL=2001b4d20a20c660b +E: ID_SERIAL_SHORT=001b4d20a20c660b +E: ID_TYPE=disk +E: ID_VENDOR=WDC +E: ID_VENDOR_ENC=WDC\x20\x20\x20\x20\x20 +E: MAJOR=8 +E: MINOR=64 +E: SUBSYSTEM=block +E: TAGS=:systemd: +E: USEC_INITIALIZED=1123351 diff --git a/src/test/common/blkdev-udevadm-info-samples/mira055.sde.devid b/src/test/common/blkdev-udevadm-info-samples/mira055.sde.devid new file mode 100644 index 000000000..8de6f598c --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/mira055.sde.devid @@ -0,0 +1 @@ +WDC_WD40EFRX-68N32N0_WD-WCC7K2ZL0V6K diff --git a/src/test/common/blkdev-udevadm-info-samples/reesi001.nvme0n1 b/src/test/common/blkdev-udevadm-info-samples/reesi001.nvme0n1 new file mode 100644 index 000000000..48eada772 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/reesi001.nvme0n1 @@ -0,0 +1,16 @@ +P: /devices/pci0000:00/0000:00:02.1/0000:04:00.0/nvme/nvme0/nvme0n1 +N: nvme0n1 +S: disk/by-id/nvme-MTFDHBG800MCG-1AN1ZABYY_ZF1000MT +E: DEVLINKS=/dev/disk/by-id/nvme-MTFDHBG800MCG-1AN1ZABYY_ZF1000MT +E: DEVNAME=/dev/nvme0n1 +E: DEVPATH=/devices/pci0000:00/0000:00:02.1/0000:04:00.0/nvme/nvme0/nvme0n1 +E: DEVTYPE=disk +E: ID_PART_TABLE_TYPE=gpt +E: ID_PART_TABLE_UUID=c206417c-54fd-4938-8223-32343c5d1057 +E: ID_SERIAL=MTFDHBG800MCG-1AN1ZABYY_ZF1000MT +E: ID_SERIAL_SHORT=ZF1000MT +E: MAJOR=259 +E: MINOR=0 +E: SUBSYSTEM=block +E: TAGS=:systemd: +E: USEC_INITIALIZED=2455622 diff --git a/src/test/common/blkdev-udevadm-info-samples/reesi001.nvme0n1.devid b/src/test/common/blkdev-udevadm-info-samples/reesi001.nvme0n1.devid new file mode 100644 index 000000000..6341dd250 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/reesi001.nvme0n1.devid @@ -0,0 +1 @@ +Micron_MTFDHBG800MCG-1AN1ZABYY_ZF1000MT diff --git a/src/test/common/blkdev-udevadm-info-samples/stud.nvme0n1 b/src/test/common/blkdev-udevadm-info-samples/stud.nvme0n1 new file mode 100644 index 000000000..e72c49768 --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/stud.nvme0n1 @@ -0,0 +1,29 @@ +P: /devices/pci0000:00/0000:00:03.0/0000:04:00.0/nvme/nvme0/nvme0n1 +N: nvme0n1 +S: disk/by-id/lvm-pv-uuid-eQ399W-P00T-Fdv1-DkWO-4bGJ-VMCN-RstGEU +S: disk/by-id/nvme-INTEL_SSDPE2MX012T4_CVPD6185002R1P2QGN +S: disk/by-id/nvme-nvme.8086-43565044363138353030325231503251474e-494e54454c205353445045324d583031325434-00000001 +S: disk/by-path/pci-0000:04:00.0-nvme-1 +E: DEVLINKS=/dev/disk/by-path/pci-0000:04:00.0-nvme-1 /dev/disk/by-id/lvm-pv-uuid-eQ399W-P00T-Fdv1-DkWO-4bGJ-VMCN-RstGEU /dev/disk/by-id/nvme-nvme.8086-43565044363138353030325231503251474e-494e54454c205353445045324d583031325434-00000001 /dev/disk/by-id/nvme-INTEL_SSDPE2MX012T4_CVPD6185002R1P2QGN +E: DEVNAME=/dev/nvme0n1 +E: DEVPATH=/devices/pci0000:00/0000:00:03.0/0000:04:00.0/nvme/nvme0/nvme0n1 +E: DEVTYPE=disk +E: ID_FS_TYPE=LVM2_member +E: ID_FS_USAGE=raid +E: ID_FS_UUID=eQ399W-P00T-Fdv1-DkWO-4bGJ-VMCN-RstGEU +E: ID_FS_UUID_ENC=eQ399W-P00T-Fdv1-DkWO-4bGJ-VMCN-RstGEU +E: ID_FS_VERSION=LVM2 001 +E: ID_MODEL=LVM PV eQ399W-P00T-Fdv1-DkWO-4bGJ-VMCN-RstGEU on /dev/nvme0n1 +E: ID_PATH=pci-0000:04:00.0-nvme-1 +E: ID_PATH_TAG=pci-0000_04_00_0-nvme-1 +E: ID_SERIAL=INTEL SSDPE2MX012T4_CVPD6185002R1P2QGN +E: ID_SERIAL_SHORT=CVPD6185002R1P2QGN +E: ID_WWN=nvme.8086-43565044363138353030325231503251474e-494e54454c205353445045324d583031325434-00000001 +E: MAJOR=259 +E: MINOR=0 +E: SUBSYSTEM=block +E: SYSTEMD_ALIAS=/dev/block/259:0 +E: SYSTEMD_READY=1 +E: SYSTEMD_WANTS=lvm2-pvscan@259:0.service +E: TAGS=:systemd: +E: USEC_INITIALIZED=13656184 diff --git a/src/test/common/blkdev-udevadm-info-samples/stud.nvme0n1.devid b/src/test/common/blkdev-udevadm-info-samples/stud.nvme0n1.devid new file mode 100644 index 000000000..70a46678d --- /dev/null +++ b/src/test/common/blkdev-udevadm-info-samples/stud.nvme0n1.devid @@ -0,0 +1 @@ +INTEL_SSDPE2MX012T4_CVPD6185002R1P2QGN diff --git a/src/test/common/dns_messages.h b/src/test/common/dns_messages.h new file mode 100644 index 000000000..1b282688a --- /dev/null +++ b/src/test/common/dns_messages.h @@ -0,0 +1,100 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2016 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ +#ifndef CEPH_TEST_DNS_MESSAGES_H +#define CEPH_TEST_DNS_MESSAGES_H + +#include "common/dns_resolve.h" +#include "gmock/gmock.h" + +u_char ns_search_msg_ok_payload[] = { + 0x00, 0x55, 0x85, 0x80, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x05, 0x09, + 0x5F, 0x63, 0x65, 0x70, 0x68, 0x2D, 0x6D, 0x6F, 0x6E, 0x04, 0x5F, 0x74, 0x63, + 0x70, 0x04, 0x63, 0x65, 0x70, 0x68, 0x03, 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x21, + 0x00, 0x01, 0xC0, 0x0C, 0x00, 0x21, 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, + 0x16, 0x00, 0x0A, 0x00, 0x28, 0x1A, 0x85, 0x03, 0x6D, 0x6F, 0x6E, 0x01, 0x61, + 0x04, 0x63, 0x65, 0x70, 0x68, 0x03, 0x63, 0x6F, 0x6D, 0x00, 0xC0, 0x0C, 0x00, + 0x21, 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x16, 0x00, 0x0A, 0x00, 0x19, + 0x1A, 0x85, 0x03, 0x6D, 0x6F, 0x6E, 0x01, 0x63, 0x04, 0x63, 0x65, 0x70, 0x68, + 0x03, 0x63, 0x6F, 0x6D, 0x00, 0xC0, 0x0C, 0x00, 0x21, 0x00, 0x01, 0x00, 0x09, + 0x3A, 0x80, 0x00, 0x16, 0x00, 0x0A, 0x00, 0x23, 0x1A, 0x85, 0x03, 0x6D, 0x6F, + 0x6E, 0x01, 0x62, 0x04, 0x63, 0x65, 0x70, 0x68, 0x03, 0x63, 0x6F, 0x6D, 0x00, + 0xC0, 0x85, 0x00, 0x02, 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x06, 0x03, + 0x6E, 0x73, 0x32, 0xC0, 0x85, 0xC0, 0x85, 0x00, 0x02, 0x00, 0x01, 0x00, 0x09, + 0x3A, 0x80, 0x00, 0x06, 0x03, 0x6E, 0x73, 0x31, 0xC0, 0x85, 0xC0, 0x5D, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0x0D, + 0xC0, 0x7F, 0x00, 0x01, 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, + 0xA8, 0x01, 0x0C, 0xC0, 0x3B, 0x00, 0x01, 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, + 0x00, 0x04, 0xC0, 0xA8, 0x01, 0x0B, 0xC0, 0xAD, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0x59, 0xC0, 0x9B, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0xFE +}; + + +u_char ns_query_msg_mon_c_payload[] = { + 0x46, 0x4D, 0x85, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x03, + 0x6D, 0x6F, 0x6E, 0x01, 0x63, 0x04, 0x63, 0x65, 0x70, 0x68, 0x03, 0x63, 0x6F, + 0x6D, 0x00, 0x00, 0x01, 0x00, 0x01, 0xC0, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0x0D, 0xC0, 0x12, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x06, 0x03, 0x6E, 0x73, 0x31, 0xC0, + 0x12, 0xC0, 0x12, 0x00, 0x02, 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x06, + 0x03, 0x6E, 0x73, 0x32, 0xC0, 0x12, 0xC0, 0x3C, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0x59, 0xC0, 0x4E, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0xFE +}; + +u_char ns_query_msg_mon_b_payload[] = { + 0x64, 0xCC, 0x85, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x03, + 0x6D, 0x6F, 0x6E, 0x01, 0x62, 0x04, 0x63, 0x65, 0x70, 0x68, 0x03, 0x63, 0x6F, + 0x6D, 0x00, 0x00, 0x01, 0x00, 0x01, 0xC0, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0x0C, 0xC0, 0x12, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x06, 0x03, 0x6E, 0x73, 0x32, 0xC0, + 0x12, 0xC0, 0x12, 0x00, 0x02, 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x06, + 0x03, 0x6E, 0x73, 0x31, 0xC0, 0x12, 0xC0, 0x4E, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0x59, 0xC0, 0x3C, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0xFE +}; + +u_char ns_query_msg_mon_a_payload[] = { + 0x86, 0xAD, 0x85, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x03, + 0x6D, 0x6F, 0x6E, 0x01, 0x61, 0x04, 0x63, 0x65, 0x70, 0x68, 0x03, 0x63, 0x6F, + 0x6D, 0x00, 0x00, 0x01, 0x00, 0x01, 0xC0, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0x0B, 0xC0, 0x12, 0x00, 0x02, + 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x06, 0x03, 0x6E, 0x73, 0x32, 0xC0, + 0x12, 0xC0, 0x12, 0x00, 0x02, 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x06, + 0x03, 0x6E, 0x73, 0x31, 0xC0, 0x12, 0xC0, 0x4E, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0x59, 0xC0, 0x3C, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x09, 0x3A, 0x80, 0x00, 0x04, 0xC0, 0xA8, 0x01, 0xFE +}; + +class MockResolvHWrapper : public ResolvHWrapper { + +public: +#ifdef HAVE_RES_NQUERY + MOCK_METHOD6(res_nquery, int(res_state s, const char *hostname, int cls, + int type, u_char *buf, int bufsz)); + + MOCK_METHOD6(res_nsearch, int(res_state s, const char *hostname, int cls, + int type, u_char *buf, int bufsz)); +#else + MOCK_METHOD5(res_query, int(const char *hostname, int cls, + int type, u_char *buf, int bufsz)); + + MOCK_METHOD5(res_search, int(const char *hostname, int cls, + int type, u_char *buf, int bufsz)); +#endif + +}; + + +#endif diff --git a/src/test/common/dns_resolve.cc b/src/test/common/dns_resolve.cc new file mode 100644 index 000000000..437004860 --- /dev/null +++ b/src/test/common/dns_resolve.cc @@ -0,0 +1,263 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2016 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ +#include <arpa/nameser_compat.h> + +#include "common/dns_resolve.h" +#include "test/common/dns_messages.h" + +#include "common/debug.h" +#include "gmock/gmock.h" + + +#include <sstream> + +#define TEST_DEBUG 20 + +#define dout_subsys ceph_subsys_ + +using namespace std; + +using ::testing::Return; +using ::testing::_; +using ::testing::SetArrayArgument; +using ::testing::DoAll; +using ::testing::StrEq; + +class DNSResolverTest : public ::testing::Test { + protected: + void SetUp() override { + g_ceph_context->_conf->subsys.set_log_level(dout_subsys, TEST_DEBUG); + } + + void TearDown() override { + DNSResolver::get_instance(nullptr); + } +}; + +TEST_F(DNSResolverTest, resolve_ip_addr) { + MockResolvHWrapper *resolvH = new MockResolvHWrapper(); + + int lena = sizeof(ns_query_msg_mon_a_payload); +#ifdef HAVE_RES_NQUERY + EXPECT_CALL(*resolvH, res_nquery(_,StrEq("mon.a.ceph.com"), C_IN, T_A,_,_)) + .WillOnce(DoAll(SetArrayArgument<4>(ns_query_msg_mon_a_payload, + ns_query_msg_mon_a_payload+lena), Return(lena))); +#else + EXPECT_CALL(*resolvH, res_query(StrEq("mon.a.ceph.com"), C_IN, T_A,_,_)) + .WillOnce(DoAll(SetArrayArgument<3>(ns_query_msg_mon_a_payload, + ns_query_msg_mon_a_payload+lena), Return(lena))); +#endif + + entity_addr_t addr; + DNSResolver::get_instance(resolvH)->resolve_ip_addr(g_ceph_context, + "mon.a.ceph.com", &addr); + + std::ostringstream os; + os << addr; + ASSERT_EQ(os.str(), "v2:192.168.1.11:0/0"); +} + +TEST_F(DNSResolverTest, resolve_ip_addr_fail) { + MockResolvHWrapper *resolvH = new MockResolvHWrapper(); + +#ifdef HAVE_RES_NQUERY + EXPECT_CALL(*resolvH, res_nquery(_,StrEq("not_exists.com"), C_IN, T_A,_,_)) + .WillOnce(Return(0)); +#else + EXPECT_CALL(*resolvH, res_query(StrEq("not_exists.com"), C_IN, T_A,_,_)) + .WillOnce(Return(0)); +#endif + + entity_addr_t addr; + int ret = DNSResolver::get_instance(resolvH)->resolve_ip_addr(g_ceph_context, + "not_exists.com", &addr); + + ASSERT_EQ(ret, -1); + std::ostringstream os; + os << addr; + ASSERT_EQ(os.str(), "-"); +} + + +TEST_F(DNSResolverTest, resolve_srv_hosts_empty_domain) { + MockResolvHWrapper *resolvH = new MockResolvHWrapper(); + + + int len = sizeof(ns_search_msg_ok_payload); + int lena = sizeof(ns_query_msg_mon_a_payload); + int lenb = sizeof(ns_query_msg_mon_b_payload); + int lenc = sizeof(ns_query_msg_mon_c_payload); + + using ::testing::InSequence; + { + InSequence s; + +#ifdef HAVE_RES_NQUERY + EXPECT_CALL(*resolvH, res_nsearch(_, StrEq("_cephmon._tcp"), C_IN, T_SRV, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(ns_search_msg_ok_payload, + ns_search_msg_ok_payload+len), Return(len))); + + EXPECT_CALL(*resolvH, res_nquery(_,StrEq("mon.a.ceph.com"), C_IN, T_A,_,_)) + .WillOnce(DoAll(SetArrayArgument<4>(ns_query_msg_mon_a_payload, + ns_query_msg_mon_a_payload+lena), Return(lena))); + + EXPECT_CALL(*resolvH, res_nquery(_, StrEq("mon.c.ceph.com"), C_IN, T_A,_,_)) + .WillOnce(DoAll(SetArrayArgument<4>(ns_query_msg_mon_c_payload, + ns_query_msg_mon_c_payload+lenc), Return(lenc))); + + EXPECT_CALL(*resolvH, res_nquery(_,StrEq("mon.b.ceph.com"), C_IN, T_A, _,_)) + .WillOnce(DoAll(SetArrayArgument<4>(ns_query_msg_mon_b_payload, + ns_query_msg_mon_b_payload+lenb), Return(lenb))); +#else + EXPECT_CALL(*resolvH, res_search(StrEq("_cephmon._tcp"), C_IN, T_SRV, _, _)) + .WillOnce(DoAll(SetArrayArgument<3>(ns_search_msg_ok_payload, + ns_search_msg_ok_payload+len), Return(len))); + + EXPECT_CALL(*resolvH, res_query(StrEq("mon.a.ceph.com"), C_IN, T_A,_,_)) + .WillOnce(DoAll(SetArrayArgument<3>(ns_query_msg_mon_a_payload, + ns_query_msg_mon_a_payload+lena), Return(lena))); + + EXPECT_CALL(*resolvH, res_query(StrEq("mon.c.ceph.com"), C_IN, T_A,_,_)) + .WillOnce(DoAll(SetArrayArgument<3>(ns_query_msg_mon_c_payload, + ns_query_msg_mon_c_payload+lenc), Return(lenc))); + + EXPECT_CALL(*resolvH, res_query(StrEq("mon.b.ceph.com"), C_IN, T_A, _,_)) + .WillOnce(DoAll(SetArrayArgument<3>(ns_query_msg_mon_b_payload, + ns_query_msg_mon_b_payload+lenb), Return(lenb))); +#endif + } + + map<string, DNSResolver::Record> records; + DNSResolver::get_instance(resolvH)->resolve_srv_hosts(g_ceph_context, "cephmon", + DNSResolver::SRV_Protocol::TCP, &records); + + ASSERT_EQ(records.size(), (unsigned int)3); + auto it = records.find("mon.a"); + ASSERT_NE(it, records.end()); + std::ostringstream os; + os << it->second.addr; + ASSERT_EQ(os.str(), "v2:192.168.1.11:6789/0"); + os.str(""); + ASSERT_EQ(it->second.priority, 10); + ASSERT_EQ(it->second.weight, 40); + it = records.find("mon.b"); + ASSERT_NE(it, records.end()); + os << it->second.addr; + ASSERT_EQ(os.str(), "v2:192.168.1.12:6789/0"); + os.str(""); + ASSERT_EQ(it->second.priority, 10); + ASSERT_EQ(it->second.weight, 35); + it = records.find("mon.c"); + ASSERT_NE(it, records.end()); + os << it->second.addr; + ASSERT_EQ(os.str(), "v2:192.168.1.13:6789/0"); + ASSERT_EQ(it->second.priority, 10); + ASSERT_EQ(it->second.weight, 25); +} + +TEST_F(DNSResolverTest, resolve_srv_hosts_full_domain) { + MockResolvHWrapper *resolvH = new MockResolvHWrapper(); + + + int len = sizeof(ns_search_msg_ok_payload); + int lena = sizeof(ns_query_msg_mon_a_payload); + int lenb = sizeof(ns_query_msg_mon_b_payload); + int lenc = sizeof(ns_query_msg_mon_c_payload); + + using ::testing::InSequence; + { + InSequence s; + +#ifdef HAVE_RES_NQUERY + EXPECT_CALL(*resolvH, res_nsearch(_, StrEq("_cephmon._tcp.ceph.com"), C_IN, T_SRV, _, _)) + .WillOnce(DoAll(SetArrayArgument<4>(ns_search_msg_ok_payload, + ns_search_msg_ok_payload+len), Return(len))); + + EXPECT_CALL(*resolvH, res_nquery(_,StrEq("mon.a.ceph.com"), C_IN, T_A,_,_)) + .WillOnce(DoAll(SetArrayArgument<4>(ns_query_msg_mon_a_payload, + ns_query_msg_mon_a_payload+lena), Return(lena))); + + EXPECT_CALL(*resolvH, res_nquery(_, StrEq("mon.c.ceph.com"), C_IN, T_A,_,_)) + .WillOnce(DoAll(SetArrayArgument<4>(ns_query_msg_mon_c_payload, + ns_query_msg_mon_c_payload+lenc), Return(lenc))); + + EXPECT_CALL(*resolvH, res_nquery(_,StrEq("mon.b.ceph.com"), C_IN, T_A, _,_)) + .WillOnce(DoAll(SetArrayArgument<4>(ns_query_msg_mon_b_payload, + ns_query_msg_mon_b_payload+lenb), Return(lenb))); +#else + EXPECT_CALL(*resolvH, res_search(StrEq("_cephmon._tcp.ceph.com"), C_IN, T_SRV, _, _)) + .WillOnce(DoAll(SetArrayArgument<3>(ns_search_msg_ok_payload, + ns_search_msg_ok_payload+len), Return(len))); + + EXPECT_CALL(*resolvH, res_query(StrEq("mon.a.ceph.com"), C_IN, T_A,_,_)) + .WillOnce(DoAll(SetArrayArgument<3>(ns_query_msg_mon_a_payload, + ns_query_msg_mon_a_payload+lena), Return(lena))); + + EXPECT_CALL(*resolvH, res_query(StrEq("mon.c.ceph.com"), C_IN, T_A,_,_)) + .WillOnce(DoAll(SetArrayArgument<3>(ns_query_msg_mon_c_payload, + ns_query_msg_mon_c_payload+lenc), Return(lenc))); + + EXPECT_CALL(*resolvH, res_query(StrEq("mon.b.ceph.com"), C_IN, T_A, _,_)) + .WillOnce(DoAll(SetArrayArgument<3>(ns_query_msg_mon_b_payload, + ns_query_msg_mon_b_payload+lenb), Return(lenb))); +#endif + } + + map<string, DNSResolver::Record> records; + DNSResolver::get_instance(resolvH)->resolve_srv_hosts(g_ceph_context, "cephmon", + DNSResolver::SRV_Protocol::TCP, "ceph.com", &records); + + ASSERT_EQ(records.size(), (unsigned int)3); + auto it = records.find("mon.a"); + ASSERT_NE(it, records.end()); + std::ostringstream os; + os << it->second.addr; + ASSERT_EQ(os.str(), "v2:192.168.1.11:6789/0"); + os.str(""); + it = records.find("mon.b"); + ASSERT_NE(it, records.end()); + os << it->second.addr; + ASSERT_EQ(os.str(), "v2:192.168.1.12:6789/0"); + os.str(""); + it = records.find("mon.c"); + ASSERT_NE(it, records.end()); + os << it->second.addr; + ASSERT_EQ(os.str(), "v2:192.168.1.13:6789/0"); +} + +TEST_F(DNSResolverTest, resolve_srv_hosts_fail) { + MockResolvHWrapper *resolvH = new MockResolvHWrapper(); + + + using ::testing::InSequence; + { + InSequence s; + +#ifdef HAVE_RES_NQUERY + EXPECT_CALL(*resolvH, res_nsearch(_, StrEq("_noservice._tcp"), C_IN, T_SRV, _, _)) + .WillOnce(Return(0)); +#else + EXPECT_CALL(*resolvH, res_search(StrEq("_noservice._tcp"), C_IN, T_SRV, _, _)) + .WillOnce(Return(0)); +#endif + } + + map<string, DNSResolver::Record> records; + int ret = DNSResolver::get_instance(resolvH)->resolve_srv_hosts( + g_ceph_context, "noservice", DNSResolver::SRV_Protocol::TCP, "", &records); + + ASSERT_EQ(0, ret); + ASSERT_TRUE(records.empty()); +} + diff --git a/src/test/common/get_command_descriptions.cc b/src/test/common/get_command_descriptions.cc new file mode 100644 index 000000000..003ebb35c --- /dev/null +++ b/src/test/common/get_command_descriptions.cc @@ -0,0 +1,131 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com> + * + * Author: Loic Dachary <loic@dachary.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library Public License for more details. + * + */ + +#include <stdio.h> +#include <signal.h> +#include "mon/Monitor.h" +#include "common/ceph_argparse.h" +#include "global/global_init.h" + +using namespace std; + +static void usage(ostream &out) +{ + out << "usage: get_command_descriptions [options ...]" << std::endl; + out << "print on stdout the result of JSON formatted options\n"; + out << "found in mon/MonCommands.h as produced by the\n"; + out << "Monitor.cc::get_command_descriptions function.\n"; + out << "Designed as a helper for ceph_argparse.py unit tests.\n"; + out << "\n"; + out << " --all all of mon/MonCommands.h \n"; + out << " --pull585 reproduce the bug fixed by #585\n"; + out << "\n"; + out << "Examples:\n"; + out << " get_command_descriptions --all\n"; + out << " get_command_descriptions --pull585\n"; +} + +static void json_print(const std::vector<MonCommand> &mon_commands) +{ + bufferlist rdata; + auto f = Formatter::create_unique("json"); + Monitor::format_command_descriptions(mon_commands, f.get(), + CEPH_FEATURES_ALL, &rdata); + string data(rdata.c_str(), rdata.length()); + cout << data << std::endl; +} + +static void all() +{ +#undef FLAG +#undef COMMAND +#undef COMMAND_WITH_FLAG + std::vector<MonCommand> mon_commands = { +#define FLAG(f) (MonCommand::FLAG_##f) +#define COMMAND(parsesig, helptext, modulename, req_perms) \ + {parsesig, helptext, modulename, req_perms, 0}, +#define COMMAND_WITH_FLAG(parsesig, helptext, modulename, req_perms, flags) \ + {parsesig, helptext, modulename, req_perms, flags}, +#include <mon/MonCommands.h> +#undef COMMAND +#undef COMMAND_WITH_FLAG + +#define COMMAND(parsesig, helptext, modulename, req_perms) \ + {parsesig, helptext, modulename, req_perms, FLAG(MGR)}, +#define COMMAND_WITH_FLAG(parsesig, helptext, modulename, req_perms, flags) \ + {parsesig, helptext, modulename, req_perms, flags | FLAG(MGR)}, +#include <mgr/MgrCommands.h> + #undef COMMAND +#undef COMMAND_WITH_FLAG + }; + + json_print(mon_commands); +} + +// syntax error https://github.com/ceph/ceph/pull/585 +static void pull585() +{ + std::vector<MonCommand> mon_commands = { + { "osd pool create " + "name=pool,type=CephPoolname " + "name=pg_num,type=CephInt,range=0,req=false " + "name=pgp_num,type=CephInt,range=0,req=false" // !!! missing trailing space + "name=properties,type=CephString,n=N,req=false,goodchars=[A-Za-z0-9-_.=]", + "create pool", "osd", "rw" } + }; + + json_print(mon_commands); +} + +int main(int argc, char **argv) { + auto args = argv_to_vec(argc, argv); + + auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT, + CODE_ENVIRONMENT_UTILITY, + CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); + common_init_finish(g_ceph_context); + + if (args.empty()) { + usage(cerr); + exit(1); + } + for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ++i) { + string err; + + if (*i == string("help") || *i == string("-h") || *i == string("--help")) { + usage(cout); + exit(0); + } else if (*i == string("--all")) { + all(); + } else if (*i == string("--pull585")) { + pull585(); + } + } +} + +/* + * Local Variables: + * compile-command: "cd ../.. ; + * make get_command_descriptions && + * ./get_command_descriptions --all --pull585" + * End: + */ + diff --git a/src/test/common/histogram.cc b/src/test/common/histogram.cc new file mode 100644 index 000000000..fbecc6722 --- /dev/null +++ b/src/test/common/histogram.cc @@ -0,0 +1,129 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2014 Inktank <info@inktank.com> + * + * LGPL-2.1 (see COPYING-LGPL2.1) or later + */ + +#include <iostream> +#include <gtest/gtest.h> + +#include "common/histogram.h" +#include "include/stringify.h" + +TEST(Histogram, Basic) { + pow2_hist_t h; + + h.add(0); + h.add(0); + h.add(0); + ASSERT_EQ(3, h.h[0]); + ASSERT_EQ(1u, h.h.size()); + + h.add(1); + ASSERT_EQ(3, h.h[0]); + ASSERT_EQ(1, h.h[1]); + ASSERT_EQ(2u, h.h.size()); + + h.add(2); + h.add(2); + ASSERT_EQ(3, h.h[0]); + ASSERT_EQ(1, h.h[1]); + ASSERT_EQ(2, h.h[2]); + ASSERT_EQ(3u, h.h.size()); +} + +TEST(Histogram, Set) { + pow2_hist_t h; + h.set_bin(0, 12); + h.set_bin(2, 12); + ASSERT_EQ(12, h.h[0]); + ASSERT_EQ(0, h.h[1]); + ASSERT_EQ(12, h.h[2]); + ASSERT_EQ(3u, h.h.size()); +} + +TEST(Histogram, Position) { + pow2_hist_t h; + uint64_t lb, ub; + h.add(0); + ASSERT_EQ(-1, h.get_position_micro(-20, &lb, &ub)); +} + +TEST(Histogram, Position1) { + pow2_hist_t h; + h.add(0); + uint64_t lb, ub; + h.get_position_micro(0, &lb, &ub); + ASSERT_EQ(0u, lb); + ASSERT_EQ(1000000u, ub); + h.add(0); + h.add(0); + h.add(0); + h.get_position_micro(0, &lb, &ub); + ASSERT_EQ(0u, lb); + ASSERT_EQ(1000000u, ub); +} + +TEST(Histogram, Position2) { + pow2_hist_t h; + h.add(1); + h.add(1); + uint64_t lb, ub; + h.get_position_micro(0, &lb, &ub); + ASSERT_EQ(0u, lb); + ASSERT_EQ(0u, ub); + h.add(0); + h.get_position_micro(0, &lb, &ub); + ASSERT_EQ(0u, lb); + ASSERT_EQ(333333u, ub); + h.get_position_micro(1, &lb, &ub); + ASSERT_EQ(333333u, lb); + ASSERT_EQ(1000000u, ub); +} + +TEST(Histogram, Position3) { + pow2_hist_t h; + h.h.resize(10, 0); + h.h[0] = 1; + h.h[5] = 1; + uint64_t lb, ub; + h.get_position_micro(4, &lb, &ub); + ASSERT_EQ(500000u, lb); + ASSERT_EQ(500000u, ub); +} + +TEST(Histogram, Position4) { + pow2_hist_t h; + h.h.resize(10, 0); + h.h[0] = UINT_MAX; + h.h[5] = UINT_MAX; + uint64_t lb, ub; + h.get_position_micro(4, &lb, &ub); + ASSERT_EQ(0u, lb); + ASSERT_EQ(0u, ub); +} + +TEST(Histogram, Decay) { + pow2_hist_t h; + h.set_bin(0, 123); + h.set_bin(3, 12); + h.set_bin(5, 1); + h.decay(1); + ASSERT_EQ(61, h.h[0]); + ASSERT_EQ(6, h.h[3]); + ASSERT_EQ(4u, h.h.size()); +} + +/* + * Local Variables: + * compile-command: "cd ../.. ; make -j4 && + * make unittest_histogram && + * valgrind --tool=memcheck --leak-check=full \ + * ./unittest_histogram + * " + * End: + */ diff --git a/src/test/common/test_allocate_unique.cc b/src/test/common/test_allocate_unique.cc new file mode 100644 index 000000000..94cb025a4 --- /dev/null +++ b/src/test/common/test_allocate_unique.cc @@ -0,0 +1,97 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "common/allocate_unique.h" +#include <string> +#include <vector> +#include <gtest/gtest.h> + +namespace { + +// allocation events recorded by logging_allocator +struct event { + size_t size; + bool allocated; // true for allocate(), false for deallocate() +}; +using event_log = std::vector<event>; + +template <typename T> +struct logging_allocator { + event_log *const log; + + using value_type = T; + + explicit logging_allocator(event_log *log) : log(log) {} + logging_allocator(const logging_allocator& other) : log(other.log) {} + + template <typename U> + logging_allocator(const logging_allocator<U>& other) : log(other.log) {} + + T* allocate(size_t n) + { + auto p = std::allocator<T>{}.allocate(n); + log->emplace_back(event{n * sizeof(T), true}); + return p; + } + void deallocate(T* p, size_t n) + { + std::allocator<T>{}.deallocate(p, n); + if (p) { + log->emplace_back(event{n * sizeof(T), false}); + } + } +}; + +} // anonymous namespace + +namespace ceph { + +TEST(AllocateUnique, Allocate) +{ + event_log log; + auto alloc = logging_allocator<char>{&log}; + { + auto p = allocate_unique<char>(alloc, 'a'); + static_assert(std::is_same_v<decltype(p), + std::unique_ptr<char, deallocator<logging_allocator<char>>>>); + ASSERT_TRUE(p); + ASSERT_EQ(1, log.size()); + EXPECT_EQ(1, log.front().size); + EXPECT_EQ(true, log.front().allocated); + } + ASSERT_EQ(2, log.size()); + EXPECT_EQ(1, log.back().size); + EXPECT_EQ(false, log.back().allocated); +} + +TEST(AllocateUnique, RebindAllocate) +{ + event_log log; + auto alloc = logging_allocator<char>{&log}; + { + auto p = allocate_unique<std::string>(alloc, "a"); + static_assert(std::is_same_v<decltype(p), + std::unique_ptr<std::string, + deallocator<logging_allocator<std::string>>>>); + ASSERT_TRUE(p); + ASSERT_EQ(1, log.size()); + EXPECT_EQ(sizeof(std::string), log.front().size); + EXPECT_EQ(true, log.front().allocated); + } + ASSERT_EQ(2, log.size()); + EXPECT_EQ(sizeof(std::string), log.back().size); + EXPECT_EQ(false, log.back().allocated); +} + +} // namespace ceph diff --git a/src/test/common/test_async_completion.cc b/src/test/common/test_async_completion.cc new file mode 100644 index 000000000..4cf4394e1 --- /dev/null +++ b/src/test/common/test_async_completion.cc @@ -0,0 +1,256 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2018 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "common/async/completion.h" +#include <optional> +#include <boost/intrusive/list.hpp> +#include <gtest/gtest.h> + +namespace ceph::async { + +using boost::system::error_code; + +struct move_only { + move_only() = default; + move_only(move_only&&) = default; + move_only& operator=(move_only&&) = default; + move_only(const move_only&) = delete; + move_only& operator=(const move_only&) = delete; +}; + +TEST(AsyncCompletion, BindHandler) +{ + auto h1 = [] (int i, char c) {}; + auto b1 = bind_handler(std::move(h1), 5, 'a'); + b1(); + const auto& c1 = b1; + c1(); + std::move(b1)(); + + // move-only types can be forwarded with 'operator() &&' + auto h2 = [] (move_only&& m) {}; + auto b2 = bind_handler(std::move(h2), move_only{}); + std::move(b2)(); + + // references bound with std::ref() can be passed to all operator() overloads + auto h3 = [] (int& c) { c++; }; + int count = 0; + auto b3 = bind_handler(std::move(h3), std::ref(count)); + EXPECT_EQ(0, count); + b3(); + EXPECT_EQ(1, count); + const auto& c3 = b3; + c3(); + EXPECT_EQ(2, count); + std::move(b3)(); + EXPECT_EQ(3, count); +} + +TEST(AsyncCompletion, ForwardHandler) +{ + // move-only types can be forwarded with 'operator() &' + auto h = [] (move_only&& m) {}; + auto b = bind_handler(std::move(h), move_only{}); + auto f = forward_handler(std::move(b)); + f(); +} + +TEST(AsyncCompletion, MoveOnly) +{ + boost::asio::io_context context; + auto ex1 = context.get_executor(); + + std::optional<error_code> ec1, ec2, ec3; + std::optional<move_only> arg3; + { + // move-only user data + using Completion = Completion<void(error_code), move_only>; + auto c = Completion::create(ex1, [&ec1] (error_code ec) { ec1 = ec; }); + Completion::post(std::move(c), boost::asio::error::operation_aborted); + EXPECT_FALSE(ec1); + } + { + // move-only handler + using Completion = Completion<void(error_code)>; + auto c = Completion::create(ex1, [&ec2, m=move_only{}] (error_code ec) { + static_cast<void>(m); + ec2 = ec; }); + Completion::post(std::move(c), boost::asio::error::operation_aborted); + EXPECT_FALSE(ec2); + } + { + // move-only arg in signature + using Completion = Completion<void(error_code, move_only)>; + auto c = Completion::create(ex1, [&] (error_code ec, move_only m) { + ec3 = ec; + arg3 = std::move(m); + }); + Completion::post(std::move(c), boost::asio::error::operation_aborted, move_only{}); + EXPECT_FALSE(ec3); + } + + context.poll(); + EXPECT_TRUE(context.stopped()); + + ASSERT_TRUE(ec1); + EXPECT_EQ(boost::asio::error::operation_aborted, *ec1); + ASSERT_TRUE(ec2); + EXPECT_EQ(boost::asio::error::operation_aborted, *ec2); + ASSERT_TRUE(ec3); + EXPECT_EQ(boost::asio::error::operation_aborted, *ec3); + ASSERT_TRUE(arg3); +} + +TEST(AsyncCompletion, VoidCompletion) +{ + boost::asio::io_context context; + auto ex1 = context.get_executor(); + + using Completion = Completion<void(error_code)>; + std::optional<error_code> ec1; + + auto c = Completion::create(ex1, [&ec1] (error_code ec) { ec1 = ec; }); + Completion::post(std::move(c), boost::asio::error::operation_aborted); + + EXPECT_FALSE(ec1); + + context.poll(); + EXPECT_TRUE(context.stopped()); + + ASSERT_TRUE(ec1); + EXPECT_EQ(boost::asio::error::operation_aborted, *ec1); +} + +TEST(AsyncCompletion, CompletionList) +{ + boost::asio::io_context context; + auto ex1 = context.get_executor(); + + using T = AsBase<boost::intrusive::list_base_hook<>>; + using Completion = Completion<void(), T>; + boost::intrusive::list<Completion> completions; + int completed = 0; + for (int i = 0; i < 3; i++) { + auto c = Completion::create(ex1, [&] { completed++; }); + completions.push_back(*c.release()); + } + completions.clear_and_dispose([] (Completion *c) { + Completion::post(std::unique_ptr<Completion>{c}); + }); + + EXPECT_EQ(0, completed); + + context.poll(); + EXPECT_TRUE(context.stopped()); + + EXPECT_EQ(3, completed); +} + +TEST(AsyncCompletion, CompletionPair) +{ + boost::asio::io_context context; + auto ex1 = context.get_executor(); + + using T = std::pair<int, std::string>; + using Completion = Completion<void(int, std::string), T>; + + std::optional<T> t; + auto c = Completion::create(ex1, [&] (int first, std::string second) { + t = T{first, std::move(second)}; + }, 2, "hello"); + + auto data = std::move(c->user_data); + Completion::post(std::move(c), data.first, std::move(data.second)); + + EXPECT_FALSE(t); + + context.poll(); + EXPECT_TRUE(context.stopped()); + + ASSERT_TRUE(t); + EXPECT_EQ(2, t->first); + EXPECT_EQ("hello", t->second); +} + +TEST(AsyncCompletion, CompletionReference) +{ + boost::asio::io_context context; + auto ex1 = context.get_executor(); + + using Completion = Completion<void(int&)>; + + auto c = Completion::create(ex1, [] (int& i) { ++i; }); + + int i = 42; + Completion::post(std::move(c), std::ref(i)); + + EXPECT_EQ(42, i); + + context.poll(); + EXPECT_TRUE(context.stopped()); + + EXPECT_EQ(43, i); +} + +struct throws_on_move { + throws_on_move() = default; + throws_on_move(throws_on_move&&) { + throw std::runtime_error("oops"); + } + throws_on_move& operator=(throws_on_move&&) { + throw std::runtime_error("oops"); + } + throws_on_move(const throws_on_move&) = default; + throws_on_move& operator=(const throws_on_move&) = default; +}; + +TEST(AsyncCompletion, ThrowOnCtor) +{ + boost::asio::io_context context; + auto ex1 = context.get_executor(); + { + using Completion = Completion<void(int&)>; + + // throw on Handler move construction + EXPECT_THROW(Completion::create(ex1, [t=throws_on_move{}] (int& i) { + static_cast<void>(t); + ++i; }), + std::runtime_error); + } + { + using T = throws_on_move; + using Completion = Completion<void(int&), T>; + + // throw on UserData construction + EXPECT_THROW(Completion::create(ex1, [] (int& i) { ++i; }, throws_on_move{}), + std::runtime_error); + } +} + +TEST(AsyncCompletion, FreeFunctions) +{ + boost::asio::io_context context; + auto ex1 = context.get_executor(); + + auto c1 = create_completion<void(), void>(ex1, [] {}); + post(std::move(c1)); + + auto c2 = create_completion<void(int), int>(ex1, [] (int) {}, 5); + defer(std::move(c2), c2->user_data); + + context.poll(); + EXPECT_TRUE(context.stopped()); +} + +} // namespace ceph::async diff --git a/src/test/common/test_async_shared_mutex.cc b/src/test/common/test_async_shared_mutex.cc new file mode 100644 index 000000000..aed6e7b00 --- /dev/null +++ b/src/test/common/test_async_shared_mutex.cc @@ -0,0 +1,428 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2018 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "common/async/shared_mutex.h" +#include <optional> +#include <gtest/gtest.h> + +namespace ceph::async { + +using executor_type = boost::asio::io_context::executor_type; +using unique_lock = std::unique_lock<SharedMutex<executor_type>>; +using shared_lock = std::shared_lock<SharedMutex<executor_type>>; + +// return a lambda that captures its error code and lock +auto capture(std::optional<boost::system::error_code>& ec, unique_lock& lock) +{ + return [&] (boost::system::error_code e, unique_lock l) { + ec = e; + lock = std::move(l); + }; +} +auto capture(std::optional<boost::system::error_code>& ec, shared_lock& lock) +{ + return [&] (boost::system::error_code e, shared_lock l) { + ec = e; + lock = std::move(l); + }; +} + +TEST(SharedMutex, async_exclusive) +{ + boost::asio::io_context context; + SharedMutex mutex(context.get_executor()); + + std::optional<boost::system::error_code> ec1, ec2, ec3; + unique_lock lock1, lock2, lock3; + + // request three exclusive locks + mutex.async_lock(capture(ec1, lock1)); + mutex.async_lock(capture(ec2, lock2)); + mutex.async_lock(capture(ec3, lock3)); + + EXPECT_FALSE(ec1); // no callbacks until poll() + EXPECT_FALSE(ec2); + EXPECT_FALSE(ec3); + + context.poll(); + EXPECT_FALSE(context.stopped()); // second lock still pending + + ASSERT_TRUE(ec1); + EXPECT_EQ(boost::system::errc::success, *ec1); + ASSERT_TRUE(lock1); + EXPECT_FALSE(ec2); + + lock1.unlock(); + + EXPECT_FALSE(ec2); + + context.poll(); + EXPECT_FALSE(context.stopped()); + + ASSERT_TRUE(ec2); + EXPECT_EQ(boost::system::errc::success, *ec2); + ASSERT_TRUE(lock2); + EXPECT_FALSE(ec3); + + lock2.unlock(); + + EXPECT_FALSE(ec3); + + context.poll(); + EXPECT_TRUE(context.stopped()); + + ASSERT_TRUE(ec3); + EXPECT_EQ(boost::system::errc::success, *ec3); + ASSERT_TRUE(lock3); +} + +TEST(SharedMutex, async_shared) +{ + boost::asio::io_context context; + SharedMutex mutex(context.get_executor()); + + std::optional<boost::system::error_code> ec1, ec2; + shared_lock lock1, lock2; + + // request two shared locks + mutex.async_lock_shared(capture(ec1, lock1)); + mutex.async_lock_shared(capture(ec2, lock2)); + + EXPECT_FALSE(ec1); // no callbacks until poll() + EXPECT_FALSE(ec2); + + context.poll(); + EXPECT_TRUE(context.stopped()); + + ASSERT_TRUE(ec1); + EXPECT_EQ(boost::system::errc::success, *ec1); + ASSERT_TRUE(lock1); + ASSERT_TRUE(ec2); + EXPECT_EQ(boost::system::errc::success, *ec2); + ASSERT_TRUE(lock2); +} + +TEST(SharedMutex, async_exclusive_while_shared) +{ + boost::asio::io_context context; + SharedMutex mutex(context.get_executor()); + + std::optional<boost::system::error_code> ec1, ec2; + shared_lock lock1; + unique_lock lock2; + + // request a shared and exclusive lock + mutex.async_lock_shared(capture(ec1, lock1)); + mutex.async_lock(capture(ec2, lock2)); + + EXPECT_FALSE(ec1); // no callbacks until poll() + EXPECT_FALSE(ec2); + + context.poll(); + EXPECT_FALSE(context.stopped()); // second lock still pending + + ASSERT_TRUE(ec1); + EXPECT_EQ(boost::system::errc::success, *ec1); + ASSERT_TRUE(lock1); + EXPECT_FALSE(ec2); + + lock1.unlock(); + + EXPECT_FALSE(ec2); + + context.poll(); + EXPECT_TRUE(context.stopped()); + + ASSERT_TRUE(ec2); + EXPECT_EQ(boost::system::errc::success, *ec2); + ASSERT_TRUE(lock2); +} + +TEST(SharedMutex, async_shared_while_exclusive) +{ + boost::asio::io_context context; + SharedMutex mutex(context.get_executor()); + + std::optional<boost::system::error_code> ec1, ec2; + unique_lock lock1; + shared_lock lock2; + + // request an exclusive and shared lock + mutex.async_lock(capture(ec1, lock1)); + mutex.async_lock_shared(capture(ec2, lock2)); + + EXPECT_FALSE(ec1); // no callbacks until poll() + EXPECT_FALSE(ec2); + + context.poll(); + EXPECT_FALSE(context.stopped()); // second lock still pending + + ASSERT_TRUE(ec1); + EXPECT_EQ(boost::system::errc::success, *ec1); + ASSERT_TRUE(lock1); + EXPECT_FALSE(ec2); + + lock1.unlock(); + + EXPECT_FALSE(ec2); + + context.poll(); + EXPECT_TRUE(context.stopped()); + + ASSERT_TRUE(ec2); + EXPECT_EQ(boost::system::errc::success, *ec2); + ASSERT_TRUE(lock2); +} + +TEST(SharedMutex, async_prioritize_exclusive) +{ + boost::asio::io_context context; + SharedMutex mutex(context.get_executor()); + + std::optional<boost::system::error_code> ec1, ec2, ec3; + shared_lock lock1, lock3; + unique_lock lock2; + + // acquire a shared lock, then request an exclusive and another shared lock + mutex.async_lock_shared(capture(ec1, lock1)); + mutex.async_lock(capture(ec2, lock2)); + mutex.async_lock_shared(capture(ec3, lock3)); + + EXPECT_FALSE(ec1); // no callbacks until poll() + EXPECT_FALSE(ec2); + EXPECT_FALSE(ec3); + + context.poll(); + EXPECT_FALSE(context.stopped()); + + ASSERT_TRUE(ec1); + EXPECT_EQ(boost::system::errc::success, *ec1); + ASSERT_TRUE(lock1); + EXPECT_FALSE(ec2); + // exclusive waiter blocks the second shared lock + EXPECT_FALSE(ec3); + + lock1.unlock(); + + EXPECT_FALSE(ec2); + EXPECT_FALSE(ec3); + + context.poll(); + EXPECT_FALSE(context.stopped()); + + ASSERT_TRUE(ec2); + EXPECT_EQ(boost::system::errc::success, *ec2); + ASSERT_TRUE(lock2); + EXPECT_FALSE(ec3); +} + +TEST(SharedMutex, async_cancel) +{ + boost::asio::io_context context; + SharedMutex mutex(context.get_executor()); + + std::optional<boost::system::error_code> ec1, ec2, ec3, ec4; + unique_lock lock1, lock2; + shared_lock lock3, lock4; + + // request 2 exclusive and shared locks + mutex.async_lock(capture(ec1, lock1)); + mutex.async_lock(capture(ec2, lock2)); + mutex.async_lock_shared(capture(ec3, lock3)); + mutex.async_lock_shared(capture(ec4, lock4)); + + EXPECT_FALSE(ec1); // no callbacks until poll() + EXPECT_FALSE(ec2); + EXPECT_FALSE(ec3); + EXPECT_FALSE(ec4); + + context.poll(); + EXPECT_FALSE(context.stopped()); + + ASSERT_TRUE(ec1); + EXPECT_EQ(boost::system::errc::success, *ec1); + ASSERT_TRUE(lock1); + EXPECT_FALSE(ec2); + EXPECT_FALSE(ec3); + EXPECT_FALSE(ec4); + + mutex.cancel(); + + EXPECT_FALSE(ec2); + EXPECT_FALSE(ec3); + EXPECT_FALSE(ec4); + + context.poll(); + EXPECT_TRUE(context.stopped()); + + ASSERT_TRUE(ec2); + EXPECT_EQ(boost::asio::error::operation_aborted, *ec2); + EXPECT_FALSE(lock2); + ASSERT_TRUE(ec3); + EXPECT_EQ(boost::asio::error::operation_aborted, *ec3); + EXPECT_FALSE(lock3); + ASSERT_TRUE(ec4); + EXPECT_EQ(boost::asio::error::operation_aborted, *ec4); + EXPECT_FALSE(lock4); +} + +TEST(SharedMutex, async_destruct) +{ + boost::asio::io_context context; + + std::optional<boost::system::error_code> ec1, ec2, ec3, ec4; + unique_lock lock1, lock2; + shared_lock lock3, lock4; + + { + SharedMutex mutex(context.get_executor()); + + // request 2 exclusive and shared locks + mutex.async_lock(capture(ec1, lock1)); + mutex.async_lock(capture(ec2, lock2)); + mutex.async_lock_shared(capture(ec3, lock3)); + mutex.async_lock_shared(capture(ec4, lock4)); + } + + EXPECT_FALSE(ec1); // no callbacks until poll() + EXPECT_FALSE(ec2); + EXPECT_FALSE(ec3); + EXPECT_FALSE(ec4); + + context.poll(); + EXPECT_TRUE(context.stopped()); + + ASSERT_TRUE(ec1); + EXPECT_EQ(boost::system::errc::success, *ec1); + ASSERT_TRUE(lock1); + ASSERT_TRUE(ec2); + EXPECT_EQ(boost::asio::error::operation_aborted, *ec2); + EXPECT_FALSE(lock2); + ASSERT_TRUE(ec3); + EXPECT_EQ(boost::asio::error::operation_aborted, *ec3); + EXPECT_FALSE(lock3); + ASSERT_TRUE(ec4); + EXPECT_EQ(boost::asio::error::operation_aborted, *ec4); + EXPECT_FALSE(lock4); +} + +// return a capture() lambda that's bound to the given executor +template <typename Executor, typename ...Args> +auto capture_ex(const Executor& ex, Args&& ...args) +{ + return boost::asio::bind_executor(ex, capture(std::forward<Args>(args)...)); +} + +TEST(SharedMutex, cross_executor) +{ + boost::asio::io_context mutex_context; + SharedMutex mutex(mutex_context.get_executor()); + + boost::asio::io_context callback_context; + auto ex2 = callback_context.get_executor(); + + std::optional<boost::system::error_code> ec1, ec2; + unique_lock lock1, lock2; + + // request two exclusive locks + mutex.async_lock(capture_ex(ex2, ec1, lock1)); + mutex.async_lock(capture_ex(ex2, ec2, lock2)); + + EXPECT_FALSE(ec1); + EXPECT_FALSE(ec2); + + mutex_context.poll(); + EXPECT_FALSE(mutex_context.stopped()); // maintains work on both executors + + EXPECT_FALSE(ec1); // no callbacks until poll() on callback_context + EXPECT_FALSE(ec2); + + callback_context.poll(); + EXPECT_FALSE(callback_context.stopped()); // second lock still pending + + ASSERT_TRUE(ec1); + EXPECT_EQ(boost::system::errc::success, *ec1); + ASSERT_TRUE(lock1); + EXPECT_FALSE(ec2); + + lock1.unlock(); + + mutex_context.poll(); + EXPECT_TRUE(mutex_context.stopped()); + + EXPECT_FALSE(ec2); + + callback_context.poll(); + EXPECT_TRUE(callback_context.stopped()); + + ASSERT_TRUE(ec2); + EXPECT_EQ(boost::system::errc::success, *ec2); + ASSERT_TRUE(lock2); +} + +TEST(SharedMutex, try_exclusive) +{ + boost::asio::io_context context; + SharedMutex mutex(context.get_executor()); + { + std::lock_guard lock{mutex}; + ASSERT_FALSE(mutex.try_lock()); // fail during exclusive + } + { + std::shared_lock lock{mutex}; + ASSERT_FALSE(mutex.try_lock()); // fail during shared + } + ASSERT_TRUE(mutex.try_lock()); + mutex.unlock(); +} + +TEST(SharedMutex, try_shared) +{ + boost::asio::io_context context; + SharedMutex mutex(context.get_executor()); + { + std::lock_guard lock{mutex}; + ASSERT_FALSE(mutex.try_lock_shared()); // fail during exclusive + } + { + std::shared_lock lock{mutex}; + ASSERT_TRUE(mutex.try_lock_shared()); // succeed during shared + mutex.unlock_shared(); + } + ASSERT_TRUE(mutex.try_lock_shared()); + mutex.unlock_shared(); +} + +TEST(SharedMutex, cancel) +{ + boost::asio::io_context context; + SharedMutex mutex(context.get_executor()); + + std::lock_guard l{mutex}; // exclusive lock blocks others + + // make synchronous lock calls in other threads + auto f1 = std::async(std::launch::async, [&] { mutex.lock(); }); + auto f2 = std::async(std::launch::async, [&] { mutex.lock_shared(); }); + + // this will race with spawned threads. just keep canceling until the + // futures are ready + const auto t = std::chrono::milliseconds(1); + do { mutex.cancel(); } while (f1.wait_for(t) != std::future_status::ready); + do { mutex.cancel(); } while (f2.wait_for(t) != std::future_status::ready); + + EXPECT_THROW(f1.get(), boost::system::system_error); + EXPECT_THROW(f2.get(), boost::system::system_error); +} + +} // namespace ceph::async diff --git a/src/test/common/test_back_trace.cc b/src/test/common/test_back_trace.cc new file mode 100644 index 000000000..97db32686 --- /dev/null +++ b/src/test/common/test_back_trace.cc @@ -0,0 +1,44 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include <boost/algorithm/string.hpp> +#include <gtest/gtest.h> +#include <regex> +#include <sstream> +#include <string> + +#include "common/BackTrace.h" +#include "common/version.h" + +// a dummy function, so we can check "foo" in the backtrace. +// do not mark this function as static or put it into an anonymous namespace, +// otherwise it's function name will be removed in the backtrace. +std::string foo() +{ + std::ostringstream oss; + oss << ceph::ClibBackTrace(1); + return oss.str(); +} + +// a typical backtrace looks like: +// +// ceph version Development (no_version) +// 1: (foo[abi:cxx11]()+0x4a) [0x5562231cf22a] +// 2: (BackTrace_Basic_Test::TestBody()+0x28) [0x5562231cf2fc] +TEST(BackTrace, Basic) { + std::string bt = foo(); + std::vector<std::string> lines; + boost::split(lines, bt, boost::is_any_of("\n")); + const unsigned lineno = 1; + ASSERT_GT(lines.size(), lineno); + ASSERT_EQ(lines[0].find(pretty_version_to_str()), 1U); + std::regex e{"^ 1: " +#ifdef __FreeBSD__ + "<foo.*>\\s" + "at\\s.*$"}; +#else + "\\(foo.*\\)\\s" + "\\[0x[[:xdigit:]]+\\]$"}; +#endif + EXPECT_TRUE(std::regex_match(lines[lineno], e)); +} diff --git a/src/test/common/test_bit_vector.cc b/src/test/common/test_bit_vector.cc new file mode 100644 index 000000000..b986c232a --- /dev/null +++ b/src/test/common/test_bit_vector.cc @@ -0,0 +1,308 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2014 Red Hat <contact@redhat.com> + * + * LGPL-2.1 (see COPYING-LGPL2.1) or later + */ + +#include <gtest/gtest.h> +#include <cmath> +#include "common/bit_vector.hpp" +#include <boost/assign/list_of.hpp> + +using namespace ceph; + +template <uint8_t _bit_count> +class TestParams { +public: + static const uint8_t BIT_COUNT = _bit_count; +}; + +template <typename T> +class BitVectorTest : public ::testing::Test { +public: + typedef BitVector<T::BIT_COUNT> bit_vector_t; +}; + +typedef ::testing::Types<TestParams<2> > BitVectorTypes; +TYPED_TEST_SUITE(BitVectorTest, BitVectorTypes); + +TYPED_TEST(BitVectorTest, resize) { + typename TestFixture::bit_vector_t bit_vector; + + size_t size = 2357; + + double elements_per_byte = 8 / bit_vector.BIT_COUNT; + + bit_vector.resize(size); + ASSERT_EQ(bit_vector.size(), size); + ASSERT_EQ(bit_vector.get_data().length(), static_cast<uint64_t>(std::ceil( + size / elements_per_byte))); +} + +TYPED_TEST(BitVectorTest, clear) { + typename TestFixture::bit_vector_t bit_vector; + + bit_vector.resize(123); + bit_vector.clear(); + ASSERT_EQ(0ull, bit_vector.size()); + ASSERT_EQ(0ull, bit_vector.get_data().length()); +} + +TYPED_TEST(BitVectorTest, bit_order) { + typename TestFixture::bit_vector_t bit_vector; + bit_vector.resize(1); + + uint8_t value = 1; + bit_vector[0] = value; + + value <<= (8 - bit_vector.BIT_COUNT); + ASSERT_EQ(value, bit_vector.get_data()[0]); +} + +TYPED_TEST(BitVectorTest, get_set) { + typename TestFixture::bit_vector_t bit_vector; + std::vector<uint64_t> ref; + + uint64_t radix = 1 << bit_vector.BIT_COUNT; + + size_t size = 1024; + bit_vector.resize(size); + ref.resize(size); + for (size_t i = 0; i < size; ++i) { + uint64_t v = rand() % radix; + ref[i] = v; + bit_vector[i] = v; + } + + const typename TestFixture::bit_vector_t &const_bit_vector(bit_vector); + for (size_t i = 0; i < size; ++i) { + ASSERT_EQ(ref[i], bit_vector[i]); + ASSERT_EQ(ref[i], const_bit_vector[i]); + } +} + +TYPED_TEST(BitVectorTest, get_buffer_extents) { + typename TestFixture::bit_vector_t bit_vector; + + uint64_t element_count = 2 * bit_vector.BLOCK_SIZE + 51; + uint64_t elements_per_byte = 8 / bit_vector.BIT_COUNT; + bit_vector.resize(element_count * elements_per_byte); + + uint64_t offset = (bit_vector.BLOCK_SIZE + 11) * elements_per_byte; + uint64_t length = (bit_vector.BLOCK_SIZE + 31) * elements_per_byte; + uint64_t data_byte_offset; + uint64_t object_byte_offset; + uint64_t byte_length; + bit_vector.get_data_extents(offset, length, &data_byte_offset, + &object_byte_offset, &byte_length); + ASSERT_EQ(bit_vector.BLOCK_SIZE, data_byte_offset); + ASSERT_EQ(bit_vector.BLOCK_SIZE + (element_count % bit_vector.BLOCK_SIZE), + byte_length); + + bit_vector.get_data_extents(1, 1, &data_byte_offset, &object_byte_offset, + &byte_length); + ASSERT_EQ(0U, data_byte_offset); + ASSERT_EQ(bit_vector.get_header_length(), object_byte_offset); + ASSERT_EQ(bit_vector.BLOCK_SIZE, byte_length); +} + +TYPED_TEST(BitVectorTest, get_header_length) { + typename TestFixture::bit_vector_t bit_vector; + + bufferlist bl; + bit_vector.encode_header(bl); + ASSERT_EQ(bl.length(), bit_vector.get_header_length()); +} + +TYPED_TEST(BitVectorTest, get_footer_offset) { + typename TestFixture::bit_vector_t bit_vector; + + bit_vector.resize(5111); + + uint64_t data_byte_offset; + uint64_t object_byte_offset; + uint64_t byte_length; + bit_vector.get_data_extents(0, bit_vector.size(), &data_byte_offset, + &object_byte_offset, &byte_length); + + ASSERT_EQ(bit_vector.get_header_length() + byte_length, + bit_vector.get_footer_offset()); +} + +TYPED_TEST(BitVectorTest, partial_decode_encode) { + typename TestFixture::bit_vector_t bit_vector; + + uint64_t elements_per_byte = 8 / bit_vector.BIT_COUNT; + bit_vector.resize(9161 * elements_per_byte); + for (uint64_t i = 0; i < bit_vector.size(); ++i) { + bit_vector[i] = i % 4; + } + + bufferlist bl; + encode(bit_vector, bl); + bit_vector.clear(); + + bufferlist header_bl; + header_bl.substr_of(bl, 0, bit_vector.get_header_length()); + auto header_it = header_bl.cbegin(); + bit_vector.decode_header(header_it); + + uint64_t object_byte_offset; + uint64_t byte_length; + bit_vector.get_header_crc_extents(&object_byte_offset, &byte_length); + ASSERT_EQ(bit_vector.get_footer_offset() + 4, object_byte_offset); + ASSERT_EQ(4ULL, byte_length); + + typedef std::pair<uint64_t, uint64_t> Extent; + typedef std::list<Extent> Extents; + + Extents extents = boost::assign::list_of( + std::make_pair(0, 1))( + std::make_pair((bit_vector.BLOCK_SIZE * elements_per_byte) - 2, 4))( + std::make_pair((bit_vector.BLOCK_SIZE * elements_per_byte) + 2, 2))( + std::make_pair((2 * bit_vector.BLOCK_SIZE * elements_per_byte) - 2, 4))( + std::make_pair((2 * bit_vector.BLOCK_SIZE * elements_per_byte) + 2, 2))( + std::make_pair(2, 2 * bit_vector.BLOCK_SIZE)); + for (Extents::iterator it = extents.begin(); it != extents.end(); ++it) { + bufferlist footer_bl; + uint64_t footer_byte_offset; + uint64_t footer_byte_length; + bit_vector.get_data_crcs_extents(it->first, it->second, &footer_byte_offset, + &footer_byte_length); + ASSERT_TRUE(footer_byte_offset + footer_byte_length <= bl.length()); + footer_bl.substr_of(bl, footer_byte_offset, footer_byte_length); + auto footer_it = footer_bl.cbegin(); + bit_vector.decode_data_crcs(footer_it, it->first); + + uint64_t element_offset = it->first; + uint64_t element_length = it->second; + uint64_t data_byte_offset; + bit_vector.get_data_extents(element_offset, element_length, + &data_byte_offset, &object_byte_offset, + &byte_length); + + bufferlist data_bl; + data_bl.substr_of(bl, bit_vector.get_header_length() + data_byte_offset, + byte_length); + auto data_it = data_bl.cbegin(); + bit_vector.decode_data(data_it, data_byte_offset); + + data_bl.clear(); + bit_vector.encode_data(data_bl, data_byte_offset, byte_length); + + footer_bl.clear(); + bit_vector.encode_data_crcs(footer_bl, it->first, it->second); + + bufferlist updated_bl; + updated_bl.substr_of(bl, 0, + bit_vector.get_header_length() + data_byte_offset); + updated_bl.append(data_bl); + + if (data_byte_offset + byte_length < bit_vector.get_footer_offset()) { + uint64_t tail_data_offset = bit_vector.get_header_length() + + data_byte_offset + byte_length; + data_bl.substr_of(bl, tail_data_offset, + bit_vector.get_footer_offset() - tail_data_offset); + updated_bl.append(data_bl); + } + + bufferlist full_footer; + full_footer.substr_of(bl, bit_vector.get_footer_offset(), + footer_byte_offset - bit_vector.get_footer_offset()); + full_footer.append(footer_bl); + + if (footer_byte_offset + footer_byte_length < bl.length()) { + bufferlist footer_bit; + auto footer_offset = footer_byte_offset + footer_byte_length; + footer_bit.substr_of(bl, footer_offset, bl.length() - footer_offset); + full_footer.append(footer_bit); + } + + updated_bl.append(full_footer); + ASSERT_EQ(bl, updated_bl); + + auto updated_it = updated_bl.cbegin(); + decode(bit_vector, updated_it); + } +} + +TYPED_TEST(BitVectorTest, header_crc) { + typename TestFixture::bit_vector_t bit_vector; + + bufferlist header; + bit_vector.encode_header(header); + + bufferlist footer; + bit_vector.encode_footer(footer); + + auto it = footer.cbegin(); + bit_vector.decode_footer(it); + + bit_vector.resize(1); + bit_vector.encode_header(header); + + it = footer.begin(); + ASSERT_THROW(bit_vector.decode_footer(it), buffer::malformed_input); +} + +TYPED_TEST(BitVectorTest, data_crc) { + typename TestFixture::bit_vector_t bit_vector1; + typename TestFixture::bit_vector_t bit_vector2; + + uint64_t elements_per_byte = 8 / bit_vector1.BIT_COUNT; + bit_vector1.resize((bit_vector1.BLOCK_SIZE + 1) * elements_per_byte); + bit_vector2.resize((bit_vector2.BLOCK_SIZE + 1) * elements_per_byte); + + uint64_t data_byte_offset; + uint64_t object_byte_offset; + uint64_t byte_length; + bit_vector1.get_data_extents(0, bit_vector1.size(), &data_byte_offset, + &object_byte_offset, &byte_length); + + bufferlist data; + bit_vector1.encode_data(data, data_byte_offset, byte_length); + + auto data_it = data.cbegin(); + bit_vector1.decode_data(data_it, data_byte_offset); + + bit_vector2[bit_vector2.size() - 1] = 1; + + bufferlist dummy_data; + bit_vector2.encode_data(dummy_data, data_byte_offset, byte_length); + + data_it = data.begin(); + ASSERT_THROW(bit_vector2.decode_data(data_it, data_byte_offset), + buffer::malformed_input); +} + +TYPED_TEST(BitVectorTest, iterator) { + typename TestFixture::bit_vector_t bit_vector; + + uint64_t radix = 1 << bit_vector.BIT_COUNT; + uint64_t size = 25 * (1ULL << 20); + uint64_t offset = 0; + + // create fragmented in-memory bufferlist layout + uint64_t resize = 0; + while (resize < size) { + resize += 4096; + if (resize > size) { + resize = size; + } + bit_vector.resize(resize); + } + + for (auto it = bit_vector.begin(); it != bit_vector.end(); ++it, ++offset) { + *it = offset % radix; + } + + offset = 123; + auto end_it = bit_vector.begin() + (size - 1024); + for (auto it = bit_vector.begin() + offset; it != end_it; ++it, ++offset) { + ASSERT_EQ(offset % radix, *it); + } +} diff --git a/src/test/common/test_blkdev.cc b/src/test/common/test_blkdev.cc new file mode 100644 index 000000000..733273ad4 --- /dev/null +++ b/src/test/common/test_blkdev.cc @@ -0,0 +1,114 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <linux/kdev_t.h> + +#include "include/types.h" +#include "common/blkdev.h" + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include <iostream> + +using namespace std; +using namespace testing; + +class MockBlkDev : public BlkDev { + public: + // pass 0 as fd, so it won't try to use the empty devname + MockBlkDev() : BlkDev(0) {}; + virtual ~MockBlkDev() {} + + MOCK_CONST_METHOD0(sysfsdir, const char*()); + MOCK_CONST_METHOD2(wholedisk, int(char* device, size_t max)); +}; + + +class BlockDevTest : public ::testing::Test { +public: + string *root; + +protected: + virtual void SetUp() { + const char *sda_name = "sda"; + const char *sdb_name = "sdb"; + const char* env = getenv("CEPH_ROOT"); + ASSERT_NE(env, nullptr) << "Environment Variable CEPH_ROOT not found!"; + root = new string(env); + *root += "/src/test/common/test_blkdev_sys_block/sys"; + + EXPECT_CALL(sda, sysfsdir()) + .WillRepeatedly(Return(root->c_str())); + EXPECT_CALL(sda, wholedisk(NotNull(), Ge(0ul))) + .WillRepeatedly( + DoAll( + SetArrayArgument<0>(sda_name, sda_name + strlen(sda_name) + 1), + Return(0))); + + EXPECT_CALL(sdb, sysfsdir()) + .WillRepeatedly(Return(root->c_str())); + EXPECT_CALL(sdb, wholedisk(NotNull(), Ge(0ul))) + .WillRepeatedly( + DoAll( + SetArrayArgument<0>(sdb_name, sdb_name + strlen(sdb_name) + 1), + Return(0))); + } + + virtual void TearDown() { + delete root; + } + + MockBlkDev sda, sdb; +}; + +TEST_F(BlockDevTest, device_model) +{ + char model[1000] = {0}; + int rc = sda.model(model, sizeof(model)); + ASSERT_EQ(0, rc); + ASSERT_STREQ(model, "myfancymodel"); +} + +TEST_F(BlockDevTest, discard) +{ + EXPECT_TRUE(sda.support_discard()); + EXPECT_TRUE(sdb.support_discard()); +} + +TEST_F(BlockDevTest, is_rotational) +{ + EXPECT_FALSE(sda.is_rotational()); + EXPECT_TRUE(sdb.is_rotational()); +} + +TEST(blkdev, _decode_model_enc) +{ + + const char *foo[][2] = { + { "WDC\\x20WDS200T2B0A-00SM50\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20\\x20", + "WDC_WDS200T2B0A-00SM50" }, + { 0, 0}, + }; + + for (unsigned i = 0; foo[i][0]; ++i) { + std::string d = _decode_model_enc(foo[i][0]); + cout << " '" << foo[i][0] << "' -> '" << d << "'" << std::endl; + ASSERT_EQ(std::string(foo[i][1]), d); + } +} + +TEST(blkdev, get_device_id) +{ + // this doesn't really test anything; it's just a way to exercise the + // get_device_id() code. + for (char c = 'a'; c < 'z'; ++c) { + char devname[4] = {'s', 'd', c, 0}; + std::string err; + auto i = get_device_id(devname, &err); + cout << "devname " << devname << " -> '" << i + << "' (" << err << ")" << std::endl; + } +} diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/add_random b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/add_random new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/add_random @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_granularity b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_granularity new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_granularity @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_max_bytes b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_max_bytes new file mode 100644 index 000000000..eba4c7ccb --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_max_bytes @@ -0,0 +1 @@ +2147450880 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_zeroes_data b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_zeroes_data new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_zeroes_data @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/hw_sector_size b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/hw_sector_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/hw_sector_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/fifo_batch b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/fifo_batch new file mode 100644 index 000000000..b6a7d89c6 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/fifo_batch @@ -0,0 +1 @@ +16 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/front_merges b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/front_merges new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/front_merges @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/read_expire b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/read_expire new file mode 100644 index 000000000..1b79f38e2 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/read_expire @@ -0,0 +1 @@ +500 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/write_expire b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/write_expire new file mode 100644 index 000000000..e9c02dad1 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/write_expire @@ -0,0 +1 @@ +5000 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/writes_starved b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/writes_starved new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/writes_starved @@ -0,0 +1 @@ +2 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iostats b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iostats new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iostats @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/logical_block_size b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/logical_block_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/logical_block_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_hw_sectors_kb b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_hw_sectors_kb new file mode 100644 index 000000000..10130bb02 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_hw_sectors_kb @@ -0,0 +1 @@ +32767 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_integrity_segments b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_integrity_segments new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_integrity_segments @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_sectors_kb b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_sectors_kb new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_sectors_kb @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_segment_size b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_segment_size new file mode 100644 index 000000000..e2ed8f4da --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_segment_size @@ -0,0 +1 @@ +65536 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_segments b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_segments new file mode 100644 index 000000000..de8febe1c --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_segments @@ -0,0 +1 @@ +168 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/minimum_io_size b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/minimum_io_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/minimum_io_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/nomerges b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/nomerges new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/nomerges @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/nr_requests b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/nr_requests new file mode 100644 index 000000000..a949a93df --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/nr_requests @@ -0,0 +1 @@ +128 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/optimal_io_size b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/optimal_io_size new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/optimal_io_size @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/physical_block_size b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/physical_block_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/physical_block_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/read_ahead_kb b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/read_ahead_kb new file mode 100644 index 000000000..a949a93df --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/read_ahead_kb @@ -0,0 +1 @@ +128 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/rotational b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/rotational new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/rotational @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/rq_affinity b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/rq_affinity new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/rq_affinity @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/scheduler b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/scheduler new file mode 100644 index 000000000..7b940d86f --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/scheduler @@ -0,0 +1 @@ +noop [deadline] cfq diff --git a/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/write_same_max_bytes b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/write_same_max_bytes new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/write_same_max_bytes @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/bar b/src/test/common/test_blkdev_sys_block/sys/block/sda/bar new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/bar diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/dev b/src/test/common/test_blkdev_sys_block/sys/block/sda/dev new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/dev diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/device/model b/src/test/common/test_blkdev_sys_block/sys/block/sda/device/model new file mode 100644 index 000000000..892d30d18 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/device/model @@ -0,0 +1 @@ +myfancymodel diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/foo b/src/test/common/test_blkdev_sys_block/sys/block/sda/foo new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/foo diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/add_random b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/add_random new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/add_random @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_granularity b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_granularity new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_granularity @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_max_bytes b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_max_bytes new file mode 100644 index 000000000..eba4c7ccb --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_max_bytes @@ -0,0 +1 @@ +2147450880 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_zeroes_data b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_zeroes_data new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_zeroes_data @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/hw_sector_size b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/hw_sector_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/hw_sector_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/fifo_batch b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/fifo_batch new file mode 100644 index 000000000..b6a7d89c6 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/fifo_batch @@ -0,0 +1 @@ +16 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/front_merges b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/front_merges new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/front_merges @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/read_expire b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/read_expire new file mode 100644 index 000000000..1b79f38e2 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/read_expire @@ -0,0 +1 @@ +500 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/write_expire b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/write_expire new file mode 100644 index 000000000..e9c02dad1 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/write_expire @@ -0,0 +1 @@ +5000 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/writes_starved b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/writes_starved new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/writes_starved @@ -0,0 +1 @@ +2 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iostats b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iostats new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iostats @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/logical_block_size b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/logical_block_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/logical_block_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_hw_sectors_kb b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_hw_sectors_kb new file mode 100644 index 000000000..10130bb02 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_hw_sectors_kb @@ -0,0 +1 @@ +32767 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_integrity_segments b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_integrity_segments new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_integrity_segments @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_sectors_kb b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_sectors_kb new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_sectors_kb @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_segment_size b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_segment_size new file mode 100644 index 000000000..e2ed8f4da --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_segment_size @@ -0,0 +1 @@ +65536 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_segments b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_segments new file mode 100644 index 000000000..de8febe1c --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_segments @@ -0,0 +1 @@ +168 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/minimum_io_size b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/minimum_io_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/minimum_io_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/nomerges b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/nomerges new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/nomerges @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/nr_requests b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/nr_requests new file mode 100644 index 000000000..a949a93df --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/nr_requests @@ -0,0 +1 @@ +128 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/optimal_io_size b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/optimal_io_size new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/optimal_io_size @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/physical_block_size b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/physical_block_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/physical_block_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/read_ahead_kb b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/read_ahead_kb new file mode 100644 index 000000000..a949a93df --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/read_ahead_kb @@ -0,0 +1 @@ +128 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/rotational b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/rotational new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/rotational @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/rq_affinity b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/rq_affinity new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/rq_affinity @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/scheduler b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/scheduler new file mode 100644 index 000000000..7b940d86f --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/scheduler @@ -0,0 +1 @@ +noop [deadline] cfq diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/write_same_max_bytes b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/write_same_max_bytes new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sda/queue/write_same_max_bytes @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/bar b/src/test/common/test_blkdev_sys_block/sys/block/sdb/bar new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/bar diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/dev b/src/test/common/test_blkdev_sys_block/sys/block/sdb/dev new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/dev diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/device/model b/src/test/common/test_blkdev_sys_block/sys/block/sdb/device/model new file mode 100644 index 000000000..892d30d18 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/device/model @@ -0,0 +1 @@ +myfancymodel diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/foo b/src/test/common/test_blkdev_sys_block/sys/block/sdb/foo new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/foo diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/add_random b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/add_random new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/add_random @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_granularity b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_granularity new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_granularity @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_max_bytes b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_max_bytes new file mode 100644 index 000000000..eba4c7ccb --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_max_bytes @@ -0,0 +1 @@ +2147450880 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_zeroes_data b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_zeroes_data new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_zeroes_data @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/hw_sector_size b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/hw_sector_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/hw_sector_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/fifo_batch b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/fifo_batch new file mode 100644 index 000000000..b6a7d89c6 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/fifo_batch @@ -0,0 +1 @@ +16 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/front_merges b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/front_merges new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/front_merges @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/read_expire b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/read_expire new file mode 100644 index 000000000..1b79f38e2 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/read_expire @@ -0,0 +1 @@ +500 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/write_expire b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/write_expire new file mode 100644 index 000000000..e9c02dad1 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/write_expire @@ -0,0 +1 @@ +5000 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/writes_starved b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/writes_starved new file mode 100644 index 000000000..0cfbf0888 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/writes_starved @@ -0,0 +1 @@ +2 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iostats b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iostats new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iostats @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/logical_block_size b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/logical_block_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/logical_block_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_hw_sectors_kb b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_hw_sectors_kb new file mode 100644 index 000000000..10130bb02 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_hw_sectors_kb @@ -0,0 +1 @@ +32767 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_integrity_segments b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_integrity_segments new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_integrity_segments @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_sectors_kb b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_sectors_kb new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_sectors_kb @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_segment_size b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_segment_size new file mode 100644 index 000000000..e2ed8f4da --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_segment_size @@ -0,0 +1 @@ +65536 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_segments b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_segments new file mode 100644 index 000000000..de8febe1c --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_segments @@ -0,0 +1 @@ +168 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/minimum_io_size b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/minimum_io_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/minimum_io_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/nomerges b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/nomerges new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/nomerges @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/nr_requests b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/nr_requests new file mode 100644 index 000000000..a949a93df --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/nr_requests @@ -0,0 +1 @@ +128 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/optimal_io_size b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/optimal_io_size new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/optimal_io_size @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/physical_block_size b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/physical_block_size new file mode 100644 index 000000000..4d0e90cbc --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/physical_block_size @@ -0,0 +1 @@ +512 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/queue b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/queue new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/queue @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/read_ahead_kb b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/read_ahead_kb new file mode 100644 index 000000000..a949a93df --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/read_ahead_kb @@ -0,0 +1 @@ +128 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/rotational b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/rotational new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/rotational @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/rq_affinity b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/rq_affinity new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/rq_affinity @@ -0,0 +1 @@ +1 diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/scheduler b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/scheduler new file mode 100644 index 000000000..7b940d86f --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/scheduler @@ -0,0 +1 @@ +noop [deadline] cfq diff --git a/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/write_same_max_bytes b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/write_same_max_bytes new file mode 100644 index 000000000..573541ac9 --- /dev/null +++ b/src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/write_same_max_bytes @@ -0,0 +1 @@ +0 diff --git a/src/test/common/test_blocked_completion.cc b/src/test/common/test_blocked_completion.cc new file mode 100644 index 000000000..71e5784af --- /dev/null +++ b/src/test/common/test_blocked_completion.cc @@ -0,0 +1,237 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2018 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + + +#include <boost/asio.hpp> +#include <boost/system/error_code.hpp> + +#include <gtest/gtest.h> + +#include "common/async/bind_handler.h" +#include "common/async/blocked_completion.h" +#include "common/async/forward_handler.h" + +using namespace std::literals; + +namespace ba = boost::asio; +namespace bs = boost::system; +namespace ca = ceph::async; + +class context_thread { + ba::io_context c; + ba::executor_work_guard<ba::io_context::executor_type> guard; + std::thread th; + +public: + context_thread() noexcept + : guard(ba::make_work_guard(c)), + th([this]() noexcept { c.run();}) {} + + ~context_thread() { + guard.reset(); + th.join(); + } + + ba::io_context& io_context() noexcept { + return c; + } + + ba::io_context::executor_type get_executor() noexcept { + return c.get_executor(); + } +}; + +struct move_only { + move_only() = default; + move_only(move_only&&) = default; + move_only& operator=(move_only&&) = default; + move_only(const move_only&) = delete; + move_only& operator=(const move_only&) = delete; +}; + +struct defaultless { + int a; + defaultless(int a) : a(a) {} +}; + +template<typename Executor, typename CompletionToken, typename... Args> +auto id(const Executor& executor, CompletionToken&& token, + Args&& ...args) +{ + ba::async_completion<CompletionToken, void(Args...)> init(token); + auto a = ba::get_associated_allocator(init.completion_handler); + executor.post(ca::forward_handler( + ca::bind_handler(std::move(init.completion_handler), + std::forward<Args>(args)...)), + a); + return init.result.get(); +} + +TEST(BlockedCompletion, Void) +{ + context_thread t; + + ba::post(t.get_executor(), ca::use_blocked); +} + +TEST(BlockedCompletion, Timer) +{ + context_thread t; + ba::steady_timer timer(t.io_context(), 50ms); + timer.async_wait(ca::use_blocked); +} + +TEST(BlockedCompletion, NoError) +{ + context_thread t; + ba::steady_timer timer(t.io_context(), 1s); + bs::error_code ec; + + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked, bs::error_code{})); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], bs::error_code{})); + EXPECT_FALSE(ec); + + int i; + EXPECT_NO_THROW(i = id(t.get_executor(), ca::use_blocked, + bs::error_code{}, 5)); + ASSERT_EQ(5, i); + EXPECT_NO_THROW(i = id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{}, 7)); + EXPECT_FALSE(ec); + ASSERT_EQ(7, i); + + float j; + + EXPECT_NO_THROW(std::tie(i, j) = id(t.get_executor(), ca::use_blocked, 9, + 3.5)); + ASSERT_EQ(9, i); + ASSERT_EQ(3.5, j); + EXPECT_NO_THROW(std::tie(i, j) = id(t.get_executor(), ca::use_blocked[ec], + 11, 2.25)); + EXPECT_FALSE(ec); + ASSERT_EQ(11, i); + ASSERT_EQ(2.25, j); +} + +TEST(BlockedCompletion, AnError) +{ + context_thread t; + ba::steady_timer timer(t.io_context(), 1s); + bs::error_code ec; + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()})); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, 5), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, 5)); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, 5, 3), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, 5, 3)); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); +} + +TEST(BlockedCompletion, MoveOnly) +{ + context_thread t; + ba::steady_timer timer(t.io_context(), 1s); + bs::error_code ec; + + + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{}, move_only{})); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{}, move_only{})); + EXPECT_FALSE(ec); + + { + auto [i, j] = id(t.get_executor(), ca::use_blocked, move_only{}, 5); + EXPECT_EQ(j, 5); + } + { + auto [i, j] = id(t.get_executor(), ca::use_blocked[ec], move_only{}, 5); + EXPECT_EQ(j, 5); + } + EXPECT_FALSE(ec); + + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, move_only{}), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, move_only{})); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, move_only{}, 3), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, + move_only{}, 3)); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); +} + +TEST(BlockedCompletion, DefaultLess) +{ + context_thread t; + ba::steady_timer timer(t.io_context(), 1s); + bs::error_code ec; + + + { + auto l = id(t.get_executor(), ca::use_blocked, bs::error_code{}, defaultless{5}); + EXPECT_EQ(5, l.a); + } + { + auto l = id(t.get_executor(), ca::use_blocked[ec], bs::error_code{}, defaultless{7}); + EXPECT_EQ(7, l.a); + } + + { + auto [i, j] = id(t.get_executor(), ca::use_blocked, defaultless{3}, 5); + EXPECT_EQ(i.a, 3); + EXPECT_EQ(j, 5); + } + { + auto [i, j] = id(t.get_executor(), ca::use_blocked[ec], defaultless{3}, 5); + EXPECT_EQ(i.a, 3); + EXPECT_EQ(j, 5); + } + EXPECT_FALSE(ec); + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, move_only{}), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, move_only{})); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, move_only{}, 3), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, + move_only{}, 3)); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); +} diff --git a/src/test/common/test_bloom_filter.cc b/src/test/common/test_bloom_filter.cc new file mode 100644 index 000000000..9eb45689b --- /dev/null +++ b/src/test/common/test_bloom_filter.cc @@ -0,0 +1,323 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2013 Inktank <info@inktank.com> + * + * LGPL-2.1 (see COPYING-LGPL2.1) or later + */ + +#include <iostream> +#include <gtest/gtest.h> + +#include "include/stringify.h" +#include "common/bloom_filter.hpp" + +TEST(BloomFilter, Basic) { + bloom_filter bf(10, .1, 1); + bf.insert("foo"); + bf.insert("bar"); + + ASSERT_TRUE(bf.contains("foo")); + ASSERT_TRUE(bf.contains("bar")); + + ASSERT_EQ(2U, bf.element_count()); +} + +TEST(BloomFilter, Empty) { + bloom_filter bf; + for (int i=0; i<100; ++i) { + ASSERT_FALSE(bf.contains((uint32_t) i)); + ASSERT_FALSE(bf.contains(stringify(i))); + } +} + +TEST(BloomFilter, Sweep) { + std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield); + std::cout.precision(5); + std::cout << "# max\tfpp\tactual\tsize\tB/insert" << std::endl; + for (int ex = 3; ex < 12; ex += 2) { + for (float fpp = .001; fpp < .5; fpp *= 4.0) { + int max = 2 << ex; + bloom_filter bf(max, fpp, 1); + bf.insert("foo"); + bf.insert("bar"); + + ASSERT_TRUE(bf.contains("foo")); + ASSERT_TRUE(bf.contains("bar")); + + for (int n = 0; n < max; n++) + bf.insert("ok" + stringify(n)); + + int test = max * 100; + int hit = 0; + for (int n = 0; n < test; n++) + if (bf.contains("asdf" + stringify(n))) + hit++; + + ASSERT_TRUE(bf.contains("foo")); + ASSERT_TRUE(bf.contains("bar")); + + double actual = (double)hit / (double)test; + + bufferlist bl; + encode(bf, bl); + + double byte_per_insert = (double)bl.length() / (double)max; + + std::cout << max << "\t" << fpp << "\t" << actual << "\t" << bl.length() << "\t" << byte_per_insert << std::endl; + ASSERT_TRUE(actual < fpp * 10); + + } + } +} + +TEST(BloomFilter, SweepInt) { + unsigned int seed = 0; + std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield); + std::cout.precision(5); + std::cout << "# max\tfpp\tactual\tsize\tB/insert\tdensity\tapprox_element_count" << std::endl; + for (int ex = 3; ex < 12; ex += 2) { + for (float fpp = .001; fpp < .5; fpp *= 4.0) { + int max = 2 << ex; + bloom_filter bf(max, fpp, 1); + bf.insert("foo"); + bf.insert("bar"); + + ASSERT_TRUE(123); + ASSERT_TRUE(456); + + // In Ceph code, the uint32_t input routines to the bloom filter + // are used with hash values that are uniformly distributed over + // the uint32_t range. To model this behavior in the test, we + // pass in values generated by a pseudo-random generator. + // To make the test reproducible anyway, use a fixed seed here, + // but a different one in each instance. + srand(seed++); + + for (int n = 0; n < max; n++) + bf.insert((uint32_t) rand()); + + int test = max * 100; + int hit = 0; + for (int n = 0; n < test; n++) + if (bf.contains((uint32_t) rand())) + hit++; + + ASSERT_TRUE(123); + ASSERT_TRUE(456); + + double actual = (double)hit / (double)test; + + bufferlist bl; + encode(bf, bl); + + double byte_per_insert = (double)bl.length() / (double)max; + + std::cout << max << "\t" << fpp << "\t" << actual << "\t" << bl.length() << "\t" << byte_per_insert + << "\t" << bf.density() << "\t" << bf.approx_unique_element_count() << std::endl; + ASSERT_TRUE(actual < fpp * 3); + ASSERT_TRUE(actual > fpp / 3); + ASSERT_TRUE(bf.density() > 0.40); + ASSERT_TRUE(bf.density() < 0.60); + } + } +} + + +TEST(BloomFilter, CompressibleSweep) { + unsigned int seed = 0; + std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield); + std::cout.precision(5); + std::cout << "# max\tins\test ins\tafter\ttgtfpp\tactual\tsize\tb/elem\n"; + float fpp = .01; + int max = 1024; + for (int div = 1; div < 10; div++) { + compressible_bloom_filter bf(max, fpp, 1); + + // See the comment in SweepInt. + srand(seed++); + + std::vector<uint32_t> values; + int t = max/div; + for (int n = 0; n < t; n++) { + uint32_t val = (uint32_t) rand(); + bf.insert(val); + values.push_back(val); + } + + unsigned est = bf.approx_unique_element_count(); + if (div > 1) + bf.compress(1.0 / div); + + for (auto val : values) + ASSERT_TRUE(bf.contains(val)); + + int test = max * 100; + int hit = 0; + for (int n = 0; n < test; n++) + if (bf.contains((uint32_t) rand())) + hit++; + + double actual = (double)hit / (double)test; + + bufferlist bl; + encode(bf, bl); + + double byte_per_insert = (double)bl.length() / (double)max; + unsigned est_after = bf.approx_unique_element_count(); + std::cout << max + << "\t" << t + << "\t" << est + << "\t" << est_after + << "\t" << fpp + << "\t" << actual + << "\t" << bl.length() << "\t" << byte_per_insert + << std::endl; + + ASSERT_TRUE(actual < fpp * 2.0); + ASSERT_TRUE(actual > fpp / 2.0); + ASSERT_TRUE(est_after < est * 2); + ASSERT_TRUE(est_after > est / 2); + } +} + + + +TEST(BloomFilter, BinSweep) { + std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield); + std::cout.precision(5); + int total_max = 16384; + float total_fpp = .01; + std::cout << "total_inserts " << total_max << " target-fpp " << total_fpp << std::endl; + for (int bins = 1; bins < 16; ++bins) { + int max = total_max / bins; + float fpp = total_fpp / bins;//pow(total_fpp, bins); + + std::vector<std::unique_ptr<bloom_filter>> ls; + bufferlist bl; + for (int i=0; i<bins; i++) { + ls.push_back(std::make_unique<bloom_filter>(max, fpp, i)); + for (int j=0; j<max; j++) { + ls.back()->insert(10000 * (i+1) + j); + } + encode(*ls.front(), bl); + } + + int hit = 0; + int test = max * 100; + for (int i=0; i<test; ++i) { + for (std::vector<std::unique_ptr<bloom_filter>>::iterator j = ls.begin(); j != ls.end(); ++j) { + if ((*j)->contains(i * 732)) { // note: sequential i does not work here; the intenral int hash is weak!! + hit++; + break; + } + } + } + + double actual = (double)hit / (double)test; + std::cout << "bins " << bins << " bin-max " << max << " bin-fpp " << fpp + << " actual-fpp " << actual + << " total-size " << bl.length() << std::endl; + } +} + +// disable these tests; doing dual insertions in consecutive filters +// appears to be equivalent to doing a single insertion in a bloom +// filter that is twice as big. +#if 0 + +// test the fpp over a sequence of bloom filters, each with unique +// items inserted into it. +// +// we expect: actual_fpp = num_filters * per_filter_fpp +TEST(BloomFilter, Sequence) { + + int max = 1024; + double fpp = .01; + for (int seq = 2; seq <= 128; seq *= 2) { + std::vector<bloom_filter*> ls; + for (int i=0; i<seq; i++) { + ls.push_back(new bloom_filter(max*2, fpp, i)); + for (int j=0; j<max; j++) { + ls.back()->insert("ok" + stringify(j) + "_" + stringify(i)); + if (ls.size() > 1) + ls[ls.size() - 2]->insert("ok" + stringify(j) + "_" + stringify(i)); + } + } + + int hit = 0; + int test = max * 100; + for (int i=0; i<test; ++i) { + for (std::vector<bloom_filter*>::iterator j = ls.begin(); j != ls.end(); ++j) { + if ((*j)->contains("bad" + stringify(i))) { + hit++; + break; + } + } + } + + double actual = (double)hit / (double)test; + std::cout << "seq " << seq << " max " << max << " fpp " << fpp << " actual " << actual << std::endl; + } +} + +// test the ffp over a sequence of bloom filters, where actual values +// are always inserted into a consecutive pair of filters. in order +// to have a false positive, we need to falsely match two consecutive +// filters. +// +// we expect: actual_fpp = num_filters * per_filter_fpp^2 +TEST(BloomFilter, SequenceDouble) { + int max = 1024; + double fpp = .01; + for (int seq = 2; seq <= 128; seq *= 2) { + std::vector<bloom_filter*> ls; + for (int i=0; i<seq; i++) { + ls.push_back(new bloom_filter(max*2, fpp, i)); + for (int j=0; j<max; j++) { + ls.back()->insert("ok" + stringify(j) + "_" + stringify(i)); + if (ls.size() > 1) + ls[ls.size() - 2]->insert("ok" + stringify(j) + "_" + stringify(i)); + } + } + + int hit = 0; + int test = max * 100; + int run = 0; + for (int i=0; i<test; ++i) { + for (std::vector<bloom_filter*>::iterator j = ls.begin(); j != ls.end(); ++j) { + if ((*j)->contains("bad" + stringify(i))) { + run++; + if (run >= 2) { + hit++; + break; + } + } else { + run = 0; + } + } + } + + double actual = (double)hit / (double)test; + std::cout << "seq " << seq << " max " << max << " fpp " << fpp << " actual " << actual + << " expected " << (fpp*fpp*(double)seq) << std::endl; + } +} + +#endif + +TEST(BloomFilter, Assignement) { + bloom_filter bf1(10, .1, 1), bf2; + + bf1.insert("foo"); + bf2 = bf1; + bf1.insert("bar"); + + ASSERT_TRUE(bf2.contains("foo")); + ASSERT_FALSE(bf2.contains("bar")); + + ASSERT_EQ(2U, bf1.element_count()); + ASSERT_EQ(1U, bf2.element_count()); +} diff --git a/src/test/common/test_bounded_key_counter.cc b/src/test/common/test_bounded_key_counter.cc new file mode 100644 index 000000000..a87394051 --- /dev/null +++ b/src/test/common/test_bounded_key_counter.cc @@ -0,0 +1,200 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2015 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ +#include "common/bounded_key_counter.h" +#include <gtest/gtest.h> + +namespace { + +// call get_highest() and return the number of callbacks +template <typename Key, typename Count> +size_t count_highest(BoundedKeyCounter<Key, Count>& counter, size_t count) +{ + size_t callbacks = 0; + counter.get_highest(count, [&callbacks] (const Key& key, Count count) { + ++callbacks; + }); + return callbacks; +} + +// call get_highest() and return the key/value pairs as a vector +template <typename Key, typename Count, + typename Vector = std::vector<std::pair<Key, Count>>> +Vector get_highest(BoundedKeyCounter<Key, Count>& counter, size_t count) +{ + Vector results; + counter.get_highest(count, [&results] (const Key& key, Count count) { + results.emplace_back(key, count); + }); + return results; +} + +} // anonymous namespace + +TEST(BoundedKeyCounter, Insert) +{ + BoundedKeyCounter<int, int> counter(2); + EXPECT_EQ(1, counter.insert(0)); // insert new key + EXPECT_EQ(2, counter.insert(0)); // increment counter + EXPECT_EQ(7, counter.insert(0, 5)); // add 5 to counter + EXPECT_EQ(1, counter.insert(1)); // insert new key + EXPECT_EQ(0, counter.insert(2)); // reject new key +} + +TEST(BoundedKeyCounter, Erase) +{ + BoundedKeyCounter<int, int> counter(10); + + counter.erase(0); // ok to erase nonexistent key + EXPECT_EQ(1, counter.insert(1, 1)); + EXPECT_EQ(2, counter.insert(2, 2)); + EXPECT_EQ(3, counter.insert(3, 3)); + counter.erase(2); + counter.erase(1); + counter.erase(3); + counter.erase(3); + EXPECT_EQ(0u, count_highest(counter, 10)); +} + +TEST(BoundedKeyCounter, Size) +{ + BoundedKeyCounter<int, int> counter(4); + EXPECT_EQ(0u, counter.size()); + EXPECT_EQ(1, counter.insert(1, 1)); + EXPECT_EQ(1u, counter.size()); + EXPECT_EQ(2, counter.insert(2, 2)); + EXPECT_EQ(2u, counter.size()); + EXPECT_EQ(3, counter.insert(3, 3)); + EXPECT_EQ(3u, counter.size()); + EXPECT_EQ(4, counter.insert(4, 4)); + EXPECT_EQ(4u, counter.size()); + EXPECT_EQ(0, counter.insert(5, 5)); // reject new key + EXPECT_EQ(4u, counter.size()); // size unchanged + EXPECT_EQ(5, counter.insert(4, 1)); // update existing key + EXPECT_EQ(4u, counter.size()); // size unchanged + counter.erase(2); + EXPECT_EQ(3u, counter.size()); + counter.erase(2); // erase duplicate + EXPECT_EQ(3u, counter.size()); // size unchanged + counter.erase(4); + EXPECT_EQ(2u, counter.size()); + counter.erase(1); + EXPECT_EQ(1u, counter.size()); + counter.erase(3); + EXPECT_EQ(0u, counter.size()); + EXPECT_EQ(6, counter.insert(6, 6)); + EXPECT_EQ(1u, counter.size()); + counter.clear(); + EXPECT_EQ(0u, counter.size()); +} + +TEST(BoundedKeyCounter, GetHighest) +{ + BoundedKeyCounter<int, int> counter(10); + using Vector = std::vector<std::pair<int, int>>; + + EXPECT_EQ(0u, count_highest(counter, 0)); // ok to request 0 + EXPECT_EQ(0u, count_highest(counter, 10)); // empty + EXPECT_EQ(0u, count_highest(counter, 999)); // ok to request count >> 10 + + EXPECT_EQ(1, counter.insert(1, 1)); + EXPECT_EQ(Vector({{1,1}}), get_highest(counter, 10)); + EXPECT_EQ(2, counter.insert(2, 2)); + EXPECT_EQ(Vector({{2,2},{1,1}}), get_highest(counter, 10)); + EXPECT_EQ(3, counter.insert(3, 3)); + EXPECT_EQ(Vector({{3,3},{2,2},{1,1}}), get_highest(counter, 10)); + EXPECT_EQ(3, counter.insert(4, 3)); // insert duplicated count=3 + // still returns 4 entries (but order of {3,3} and {4,3} is unspecified) + EXPECT_EQ(4u, count_highest(counter, 10)); + counter.erase(3); + EXPECT_EQ(Vector({{4,3},{2,2},{1,1}}), get_highest(counter, 10)); + EXPECT_EQ(0u, count_highest(counter, 0)); // requesting 0 still returns 0 +} + +TEST(BoundedKeyCounter, Clear) +{ + BoundedKeyCounter<int, int> counter(2); + EXPECT_EQ(1, counter.insert(0)); // insert new key + EXPECT_EQ(1, counter.insert(1)); // insert new key + EXPECT_EQ(2u, count_highest(counter, 2)); // return 2 entries + + counter.clear(); + + EXPECT_EQ(0u, count_highest(counter, 2)); // return 0 entries + EXPECT_EQ(1, counter.insert(1)); // insert new key + EXPECT_EQ(1, counter.insert(2)); // insert new unique key + EXPECT_EQ(2u, count_highest(counter, 2)); // return 2 entries +} + +// tests for partial sort and invalidation +TEST(BoundedKeyCounter, GetNumSorted) +{ + struct MockCounter : public BoundedKeyCounter<int, int> { + using BoundedKeyCounter<int, int>::BoundedKeyCounter; + // expose as public for testing sort invalidations + using BoundedKeyCounter<int, int>::get_num_sorted; + }; + + MockCounter counter(10); + + EXPECT_EQ(0u, counter.get_num_sorted()); + EXPECT_EQ(0u, count_highest(counter, 10)); + EXPECT_EQ(0u, counter.get_num_sorted()); + + EXPECT_EQ(2, counter.insert(2, 2)); + EXPECT_EQ(3, counter.insert(3, 3)); + EXPECT_EQ(4, counter.insert(4, 4)); + EXPECT_EQ(0u, counter.get_num_sorted()); + + EXPECT_EQ(0u, count_highest(counter, 0)); + EXPECT_EQ(0u, counter.get_num_sorted()); + EXPECT_EQ(1u, count_highest(counter, 1)); + EXPECT_EQ(1u, counter.get_num_sorted()); + EXPECT_EQ(2u, count_highest(counter, 2)); + EXPECT_EQ(2u, counter.get_num_sorted()); + EXPECT_EQ(3u, count_highest(counter, 10)); + EXPECT_EQ(3u, counter.get_num_sorted()); + + EXPECT_EQ(1, counter.insert(1, 1)); // insert at bottom does not invalidate + EXPECT_EQ(3u, counter.get_num_sorted()); + + EXPECT_EQ(4u, count_highest(counter, 10)); + EXPECT_EQ(4u, counter.get_num_sorted()); + + EXPECT_EQ(5, counter.insert(5, 5)); // insert at top invalidates sort + EXPECT_EQ(0u, counter.get_num_sorted()); + + EXPECT_EQ(0u, count_highest(counter, 0)); + EXPECT_EQ(0u, counter.get_num_sorted()); + EXPECT_EQ(1u, count_highest(counter, 1)); + EXPECT_EQ(1u, counter.get_num_sorted()); + EXPECT_EQ(2u, count_highest(counter, 2)); + EXPECT_EQ(2u, counter.get_num_sorted()); + EXPECT_EQ(3u, count_highest(counter, 3)); + EXPECT_EQ(3u, counter.get_num_sorted()); + EXPECT_EQ(4u, count_highest(counter, 4)); + EXPECT_EQ(4u, counter.get_num_sorted()); + EXPECT_EQ(5u, count_highest(counter, 10)); + EXPECT_EQ(5u, counter.get_num_sorted()); + + // updating an existing counter only invalidates entries <= that counter + EXPECT_EQ(2, counter.insert(1)); // invalidates {1,2} and {2,2} + EXPECT_EQ(3u, counter.get_num_sorted()); + + EXPECT_EQ(5u, count_highest(counter, 10)); + EXPECT_EQ(5u, counter.get_num_sorted()); + + counter.clear(); // invalidates sort + EXPECT_EQ(0u, counter.get_num_sorted()); +} + diff --git a/src/test/common/test_cdc.cc b/src/test/common/test_cdc.cc new file mode 100644 index 000000000..620ecf467 --- /dev/null +++ b/src/test/common/test_cdc.cc @@ -0,0 +1,163 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include <vector> +#include <cstring> +#include <random> + +#include "include/types.h" +#include "include/buffer.h" + +#include "common/CDC.h" +#include "gtest/gtest.h" + +using namespace std; + +class CDCTest : public ::testing::Test, + public ::testing::WithParamInterface<const char*> { +public: + std::unique_ptr<CDC> cdc; + + CDCTest() { + auto plugin = GetParam(); + cdc = CDC::create(plugin, 18); + } +}; + +TEST_P(CDCTest, insert_front) +{ + if (GetParam() == "fixed"s) return; + for (int frontlen = 1; frontlen < 163840; frontlen *= 3) { + bufferlist bl1, bl2; + generate_buffer(4*1024*1024, &bl1); + generate_buffer(frontlen, &bl2); + bl2.append(bl1); + bl2.rebuild(); + + vector<pair<uint64_t, uint64_t>> chunks1, chunks2; + cdc->calc_chunks(bl1, &chunks1); + cdc->calc_chunks(bl2, &chunks2); + cout << "1: " << chunks1 << std::endl; + cout << "2: " << chunks2 << std::endl; + + ASSERT_GE(chunks2.size(), chunks1.size()); + int match = 0; + for (unsigned i = 0; i < chunks1.size(); ++i) { + unsigned j = i + (chunks2.size() - chunks1.size()); + if (chunks1[i].first + frontlen == chunks2[j].first && + chunks1[i].second == chunks2[j].second) { + match++; + } + } + ASSERT_GE(match, chunks1.size() - 1); + } +} + +TEST_P(CDCTest, insert_middle) +{ + if (GetParam() == "fixed"s) return; + for (int frontlen = 1; frontlen < 163840; frontlen *= 3) { + bufferlist bl1, bl2; + generate_buffer(4*1024*1024, &bl1); + bufferlist f, m, e; + generate_buffer(frontlen, &m); + f.substr_of(bl1, 0, bl1.length() / 2); + e.substr_of(bl1, bl1.length() / 2, bl1.length() / 2); + bl2 = f; + bl2.append(m); + bl2.append(e); + bl2.rebuild(); + + vector<pair<uint64_t, uint64_t>> chunks1, chunks2; + cdc->calc_chunks(bl1, &chunks1); + cdc->calc_chunks(bl2, &chunks2); + cout << "1: " << chunks1 << std::endl; + cout << "2: " << chunks2 << std::endl; + + ASSERT_GE(chunks2.size(), chunks1.size()); + int match = 0; + unsigned i; + for (i = 0; i < chunks1.size()/2; ++i) { + unsigned j = i; + if (chunks1[i].first == chunks2[j].first && + chunks1[i].second == chunks2[j].second) { + match++; + } + } + for (; i < chunks1.size(); ++i) { + unsigned j = i + (chunks2.size() - chunks1.size()); + if (chunks1[i].first + frontlen == chunks2[j].first && + chunks1[i].second == chunks2[j].second) { + match++; + } + } + ASSERT_GE(match, chunks1.size() - 2); + } +} + +TEST_P(CDCTest, specific_result) +{ + map<string,vector<pair<uint64_t,uint64_t>>> expected = { + {"fixed", { {0, 262144}, {262144, 262144}, {524288, 262144}, {786432, 262144}, {1048576, 262144}, {1310720, 262144}, {1572864, 262144}, {1835008, 262144}, {2097152, 262144}, {2359296, 262144}, {2621440, 262144}, {2883584, 262144}, {3145728, 262144}, {3407872, 262144}, {3670016, 262144}, {3932160, 262144} }}, + {"fastcdc", { {0, 151460}, {151460, 441676}, {593136, 407491}, {1000627, 425767}, {1426394, 602875}, {2029269, 327307}, {2356576, 155515}, {2512091, 159392}, {2671483, 829416}, {3500899, 539667}, {4040566, 153738}}}, + }; + + bufferlist bl; + generate_buffer(4*1024*1024, &bl); + vector<pair<uint64_t,uint64_t>> chunks; + cdc->calc_chunks(bl, &chunks); + ASSERT_EQ(chunks, expected[GetParam()]); +} + + +void do_size_histogram(CDC& cdc, bufferlist& bl, + map<int,int> *h) +{ + vector<pair<uint64_t, uint64_t>> chunks; + cdc.calc_chunks(bl, &chunks); + uint64_t total = 0; + uint64_t num = 0; + for (auto& i : chunks) { + //unsigned b = i.second & 0xfffff000; + unsigned b = 1 << (cbits(i.second - 1)); + (*h)[b]++; + ++num; + total += i.second; + } + (*h)[0] = total / num; +} + +void print_histogram(map<int,int>& h) +{ + cout << "size\tcount" << std::endl; + for (auto i : h) { + if (i.first) { + cout << i.first << "\t" << i.second << std::endl; + } else { + cout << "avg\t" << i.second << std::endl; + } + } +} + +TEST_P(CDCTest, chunk_random) +{ + map<int,int> h; + for (int i = 0; i < 32; ++i) { + cout << "."; + cout.flush(); + bufferlist r; + generate_buffer(16*1024*1024, &r, i); + do_size_histogram(*cdc, r, &h); + } + cout << std::endl; + print_histogram(h); +} + + +INSTANTIATE_TEST_SUITE_P( + CDC, + CDCTest, + ::testing::Values( + "fixed", // note: we skip most tests bc this is not content-based + "fastcdc" + )); diff --git a/src/test/common/test_ceph_timer.cc b/src/test/common/test_ceph_timer.cc new file mode 100644 index 000000000..12391c7ea --- /dev/null +++ b/src/test/common/test_ceph_timer.cc @@ -0,0 +1,163 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <chrono> +#include <future> +#include <vector> + +#include <gtest/gtest.h> + +#include "common/ceph_timer.h" + +using namespace std::literals; + +namespace { +template<typename TC> +void run_some() +{ + static constexpr auto MAX_FUTURES = 5; + ceph::timer<TC> timer; + std::vector<std::future<void>> futures; + for (auto i = 0; i < MAX_FUTURES; ++i) { + auto t = TC::now() + 2s; + std::promise<void> p; + futures.push_back(p.get_future()); + timer.add_event(t, [p = std::move(p)]() mutable { + p.set_value(); + }); + } + for (auto& f : futures) + f.get(); +} + +template<typename TC> +void run_orderly() +{ + ceph::timer<TC> timer; + + std::future<typename TC::time_point> first; + std::future<typename TC::time_point> second; + + + { + std::promise<typename TC::time_point> p; + second = p.get_future(); + timer.add_event(4s, [p = std::move(p)]() mutable { + p.set_value(TC::now()); + }); + } + { + std::promise<typename TC::time_point> p; + first = p.get_future(); + timer.add_event(2s, [p = std::move(p)]() mutable { + p.set_value(TC::now()); + }); + } + + EXPECT_LT(first.get(), second.get()); +} + +struct Destructo { + bool armed = true; + std::promise<void> p; + + Destructo(std::promise<void>&& p) : p(std::move(p)) {} + Destructo(const Destructo&) = delete; + Destructo& operator =(const Destructo&) = delete; + Destructo(Destructo&& rhs) { + p = std::move(rhs.p); + armed = rhs.armed; + rhs.armed = false; + } + Destructo& operator =(Destructo& rhs) { + p = std::move(rhs.p); + rhs.armed = false; + armed = rhs.armed; + rhs.armed = false; + return *this; + } + + ~Destructo() { + if (armed) + p.set_value(); + } + void operator ()() const { + FAIL(); + } +}; + +template<typename TC> +void cancel_all() +{ + ceph::timer<TC> timer; + static constexpr auto MAX_FUTURES = 5; + std::vector<std::future<void>> futures; + for (auto i = 0; i < MAX_FUTURES; ++i) { + std::promise<void> p; + futures.push_back(p.get_future()); + timer.add_event(100s + i*1s, Destructo(std::move(p))); + } + timer.cancel_all_events(); + for (auto& f : futures) + f.get(); +} + +template<typename TC> +void cancellation() +{ + ceph::timer<TC> timer; + { + std::promise<void> p; + auto f = p.get_future(); + auto e = timer.add_event(100s, Destructo(std::move(p))); + EXPECT_TRUE(timer.cancel_event(e)); + } + { + std::promise<void> p; + auto f = p.get_future(); + auto e = timer.add_event(1s, [p = std::move(p)]() mutable { + p.set_value(); + }); + f.get(); + EXPECT_FALSE(timer.cancel_event(e)); + } +} +} + +TEST(RunSome, Steady) +{ + run_some<std::chrono::steady_clock>(); +} +TEST(RunSome, Wall) +{ + run_some<std::chrono::system_clock>(); +} + +TEST(RunOrderly, Steady) +{ + run_orderly<std::chrono::steady_clock>(); +} +TEST(RunOrderly, Wall) +{ + run_orderly<std::chrono::system_clock>(); +} + +TEST(CancelAll, Steady) +{ + cancel_all<std::chrono::steady_clock>(); +} +TEST(CancelAll, Wall) +{ + cancel_all<std::chrono::system_clock>(); +} diff --git a/src/test/common/test_config.cc b/src/test/common/test_config.cc new file mode 100644 index 000000000..a70d567a4 --- /dev/null +++ b/src/test/common/test_config.cc @@ -0,0 +1,313 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2014 Cloudwatt <libre.licensing@cloudwatt.com> + * + * Author: Loic Dachary <loic@dachary.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library Public License for more details. + * + * + */ +#include "common/config_proxy.h" +#include "common/errno.h" +#include "gtest/gtest.h" +#include "common/hostname.h" + +using namespace std; + +extern std::string exec(const char* cmd); // defined in test_hostname.cc + +class test_config_proxy : public ConfigProxy, public ::testing::Test { +public: + + test_config_proxy() + : ConfigProxy{true}, Test() + {} + + void test_expand_meta() { + // successfull meta expansion $run_dir and ${run_dir} + { + ostringstream oss; + std::string before = " BEFORE "; + std::string after = " AFTER "; + + std::string val(before + "$run_dir${run_dir}" + after); + early_expand_meta(val, &oss); + EXPECT_EQ(before + "/var/run/ceph/var/run/ceph" + after, val); + EXPECT_EQ("", oss.str()); + } + { + ostringstream oss; + std::string before = " BEFORE "; + std::string after = " AFTER "; + + std::string val(before + "$$1$run_dir$2${run_dir}$3$" + after); + early_expand_meta(val, &oss); + EXPECT_EQ(before + "$$1/var/run/ceph$2/var/run/ceph$3$" + after, val); + EXPECT_EQ("", oss.str()); + } + { + ostringstream oss; + std::string before = " BEFORE "; + std::string after = " AFTER "; + std::string val(before + "$host${host}" + after); + early_expand_meta(val, &oss); + std::string hostname = ceph_get_short_hostname(); + EXPECT_EQ(before + hostname + hostname + after, val); + EXPECT_EQ("", oss.str()); + } + // no meta expansion if variables are unknown + { + ostringstream oss; + std::string expected = "expect $foo and ${bar} to not expand"; + std::string val = expected; + early_expand_meta(val, &oss); + EXPECT_EQ(expected, val); + EXPECT_EQ("", oss.str()); + } + // recursive variable expansion + { + std::string host = "localhost"; + EXPECT_EQ(0, set_val("host", host.c_str())); + + std::string mon_host = "$cluster_network"; + EXPECT_EQ(0, set_val("mon_host", mon_host.c_str())); + + std::string lockdep = "true"; + EXPECT_EQ(0, set_val("lockdep", lockdep.c_str())); + + std::string cluster_network = "$public_network $public_network $lockdep $host"; + EXPECT_EQ(0, set_val("cluster_network", cluster_network.c_str())); + + std::string public_network = "NETWORK"; + EXPECT_EQ(0, set_val("public_network", public_network.c_str())); + + ostringstream oss; + std::string val = "$mon_host"; + early_expand_meta(val, &oss); + EXPECT_EQ(public_network + " " + + public_network + " " + + lockdep + " " + + "localhost", val); + EXPECT_EQ("", oss.str()); + } + // variable expansion loops are non fatal + { + std::string mon_host = "$cluster_network"; + EXPECT_EQ(0, set_val("mon_host", mon_host.c_str())); + + std::string cluster_network = "$public_network"; + EXPECT_EQ(0, set_val("cluster_network", cluster_network.c_str())); + + std::string public_network = "$mon_host"; + EXPECT_EQ(0, set_val("public_network", public_network.c_str())); + + ostringstream oss; + std::string val = "$mon_host"; + early_expand_meta(val, &oss); + EXPECT_EQ("$mon_host", val); + const char *expected_oss = + "variable expansion loop at mon_host=$cluster_network\n" + "expansion stack:\n" + "public_network=$mon_host\n" + "cluster_network=$public_network\n" + "mon_host=$cluster_network\n"; + EXPECT_EQ(expected_oss, oss.str()); + } + } +}; + +TEST_F(test_config_proxy, expand_meta) +{ + test_expand_meta(); +} + +TEST(md_config_t, parse_env) +{ + { + ConfigProxy conf{false}; + setenv("POD_MEMORY_REQUEST", "1", 1); + conf.parse_env(CEPH_ENTITY_TYPE_OSD); + } + { + ConfigProxy conf{false}; + setenv("POD_MEMORY_REQUEST", "0", 1); + conf.parse_env(CEPH_ENTITY_TYPE_OSD); + } + { + ConfigProxy conf{false}; + setenv("CEPH_KEYRING", "", 1); + conf.parse_env(CEPH_ENTITY_TYPE_OSD); + } +} + +TEST(md_config_t, set_val) +{ + int buf_size = 1024; + ConfigProxy conf{false}; + { + char *run_dir = (char*)malloc(buf_size); + EXPECT_EQ(0, conf.get_val("run_dir", &run_dir, buf_size)); + EXPECT_EQ(0, conf.set_val("admin_socket", "$run_dir")); + char *admin_socket = (char*)malloc(buf_size); + EXPECT_EQ(0, conf.get_val("admin_socket", &admin_socket, buf_size)); + EXPECT_EQ(std::string(run_dir), std::string(admin_socket)); + free(run_dir); + free(admin_socket); + } + // set_val should support SI conversion + { + auto expected = Option::size_t{512 << 20}; + EXPECT_EQ(0, conf.set_val("mgr_osd_bytes", "512M", nullptr)); + EXPECT_EQ(expected, conf.get_val<Option::size_t>("mgr_osd_bytes")); + EXPECT_EQ(-EINVAL, conf.set_val("mgr_osd_bytes", "512 bits", nullptr)); + EXPECT_EQ(expected, conf.get_val<Option::size_t>("mgr_osd_bytes")); + } + // set_val should support 1 days 2 hours 4 minutes + { + using namespace std::chrono; + const string s{"1 days 2 hours 4 minutes"}; + using days_t = duration<int, std::ratio<3600 * 24>>; + auto expected = (duration_cast<seconds>(days_t{1}) + + duration_cast<seconds>(hours{2}) + + duration_cast<seconds>(minutes{4})); + EXPECT_EQ(0, conf.set_val("mgr_tick_period", + "1 days 2 hours 4 minutes", nullptr)); + EXPECT_EQ(expected.count(), conf.get_val<seconds>("mgr_tick_period").count()); + EXPECT_EQ(-EINVAL, conf.set_val("mgr_tick_period", "21 centuries", nullptr)); + EXPECT_EQ(expected.count(), conf.get_val<seconds>("mgr_tick_period").count()); + } + + using namespace std::chrono; + + using days_t = duration<int, std::ratio<3600 * 24>>; + + struct testcase { + std::string s; + std::chrono::seconds r; + }; + std::vector<testcase> good = { + { "23"s, duration_cast<seconds>(seconds{23}) }, + { " 23 "s, duration_cast<seconds>(seconds{23}) }, + { " 23s "s, duration_cast<seconds>(seconds{23}) }, + { " 23 s "s, duration_cast<seconds>(seconds{23}) }, + { " 23 sec "s, duration_cast<seconds>(seconds{23}) }, + { "23 second "s, duration_cast<seconds>(seconds{23}) }, + { "23 seconds"s, duration_cast<seconds>(seconds{23}) }, + { "2m5s"s, duration_cast<seconds>(seconds{2*60+5}) }, + { "2 m 5 s "s, duration_cast<seconds>(seconds{2*60+5}) }, + { "2 m5"s, duration_cast<seconds>(seconds{2*60+5}) }, + { "2 min5"s, duration_cast<seconds>(seconds{2*60+5}) }, + { "2 minutes 5"s, duration_cast<seconds>(seconds{2*60+5}) }, + { "1w"s, duration_cast<seconds>(seconds{3600*24*7}) }, + { "1wk"s, duration_cast<seconds>(seconds{3600*24*7}) }, + { "1week"s, duration_cast<seconds>(seconds{3600*24*7}) }, + { "1weeks"s, duration_cast<seconds>(seconds{3600*24*7}) }, + { "1month"s, duration_cast<seconds>(seconds{3600*24*30}) }, + { "1months"s, duration_cast<seconds>(seconds{3600*24*30}) }, + { "1mo"s, duration_cast<seconds>(seconds{3600*24*30}) }, + { "1y"s, duration_cast<seconds>(seconds{3600*24*365}) }, + { "1yr"s, duration_cast<seconds>(seconds{3600*24*365}) }, + { "1year"s, duration_cast<seconds>(seconds{3600*24*365}) }, + { "1years"s, duration_cast<seconds>(seconds{3600*24*365}) }, + { "1d2h3m4s"s, + duration_cast<seconds>(days_t{1}) + + duration_cast<seconds>(hours{2}) + + duration_cast<seconds>(minutes{3}) + + duration_cast<seconds>(seconds{4}) }, + { "1 days 2 hours 4 minutes"s, + duration_cast<seconds>(days_t{1}) + + duration_cast<seconds>(hours{2}) + + duration_cast<seconds>(minutes{4}) }, + }; + + for (auto& i : good) { + cout << "good: " << i.s << " -> " << i.r.count() << std::endl; + EXPECT_EQ(0, conf.set_val("mgr_tick_period", i.s, nullptr)); + EXPECT_EQ(i.r.count(), conf.get_val<seconds>("mgr_tick_period").count()); + } + + std::vector<std::string> bad = { + "12x", + "_ 12", + "1 2", + "21 centuries", + "1 y m", + }; + for (auto& i : bad) { + std::stringstream err; + EXPECT_EQ(-EINVAL, conf.set_val("mgr_tick_period", i, &err)); + cout << "bad: " << i << " -> " << err.str() << std::endl; + } + + for (int i = 0; i < 100; ++i) { + std::chrono::seconds j = std::chrono::seconds(rand()); + string s = exact_timespan_str(j); + std::chrono::seconds k = parse_timespan(s); + cout << "rt: " << j.count() << " -> " << s << " -> " << k.count() << std::endl; + EXPECT_EQ(j.count(), k.count()); + } +} + +TEST(Option, validation) +{ + Option opt_int("foo", Option::TYPE_INT, Option::LEVEL_BASIC); + opt_int.set_min_max(5, 10); + + std::string msg; + EXPECT_EQ(-EINVAL, opt_int.validate(Option::value_t(int64_t(4)), &msg)); + EXPECT_EQ(-EINVAL, opt_int.validate(Option::value_t(int64_t(11)), &msg)); + EXPECT_EQ(0, opt_int.validate(Option::value_t(int64_t(7)), &msg)); + + Option opt_enum("foo", Option::TYPE_STR, Option::LEVEL_BASIC); + opt_enum.set_enum_allowed({"red", "blue"}); + EXPECT_EQ(0, opt_enum.validate(Option::value_t(std::string("red")), &msg)); + EXPECT_EQ(0, opt_enum.validate(Option::value_t(std::string("blue")), &msg)); + EXPECT_EQ(-EINVAL, opt_enum.validate(Option::value_t(std::string("green")), &msg)); + + Option opt_validator("foo", Option::TYPE_INT, Option::LEVEL_BASIC); + opt_validator.set_validator([](std::string *value, std::string *error_message){ + if (*value == std::string("one")) { + *value = "1"; + return 0; + } else if (*value == std::string("666")) { + return -EINVAL; + } else { + return 0; + } + }); + + std::string input = "666"; // An explicitly forbidden value + EXPECT_EQ(-EINVAL, opt_validator.pre_validate(&input, &msg)); + EXPECT_EQ(input, "666"); + + input = "123"; // A permitted value with no special behaviour + EXPECT_EQ(0, opt_validator.pre_validate(&input, &msg)); + EXPECT_EQ(input, "123"); + + input = "one"; // A value that has a magic conversion + EXPECT_EQ(0, opt_validator.pre_validate(&input, &msg)); + EXPECT_EQ(input, "1"); +} + +/* + * Local Variables: + * compile-command: "cd ../.. ; + * make unittest_config && + * valgrind \ + * --max-stackframe=20000000 --tool=memcheck \ + * ./unittest_config # --gtest_filter=md_config_t.set_val + * " + * End: + */ diff --git a/src/test/common/test_context.cc b/src/test/common/test_context.cc new file mode 100644 index 000000000..2c846a9ae --- /dev/null +++ b/src/test/common/test_context.cc @@ -0,0 +1,145 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2014 Cloudwatt <libre.licensing@cloudwatt.com> + * + * Author: Loic Dachary <loic@dachary.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library Public License for more details. + * + * + */ +#include "gtest/gtest.h" +#include "include/types.h" +#include "include/msgr.h" +#include "common/ceph_context.h" +#include "common/config_proxy.h" +#include "log/Log.h" + +using namespace std; + +TEST(CephContext, do_command) +{ + CephContext *cct = (new CephContext(CEPH_ENTITY_TYPE_CLIENT))->get(); + + cct->_conf->cluster = "ceph"; + + string key("key"); + string value("value"); + cct->_conf.set_val(key.c_str(), value.c_str()); + cmdmap_t cmdmap; + cmdmap["var"] = key; + + { + stringstream ss; + bufferlist out; + std::unique_ptr<Formatter> f{Formatter::create_unique("xml", "xml")}; + cct->do_command("config get", cmdmap, f.get(), ss, &out); + f->flush(out); + string s(out.c_str(), out.length()); + EXPECT_EQ("<config_get><key>" + value + "</key></config_get>", s); + } + + { + stringstream ss; + bufferlist out; + cmdmap_t bad_cmdmap; // no 'var' field + std::unique_ptr<Formatter> f{Formatter::create_unique("xml", "xml")}; + int r = cct->do_command("config get", bad_cmdmap, f.get(), ss, &out); + if (r >= 0) { + f->flush(out); + } + string s(out.c_str(), out.length()); + EXPECT_EQ(-EINVAL, r); + EXPECT_EQ("", s); + EXPECT_EQ("", ss.str()); // no error string :/ + } + { + stringstream ss; + bufferlist out; + cmdmap_t bad_cmdmap; + bad_cmdmap["var"] = string("doesnotexist123"); + std::unique_ptr<Formatter> f{Formatter::create_unique("xml", "xml")}; + int r = cct->do_command("config help", bad_cmdmap, f.get(), ss, &out); + if (r >= 0) { + f->flush(out); + } + string s(out.c_str(), out.length()); + EXPECT_EQ(-ENOENT, r); + EXPECT_EQ("", s); + EXPECT_EQ("Setting not found: 'doesnotexist123'", ss.str()); + } + + { + stringstream ss; + bufferlist out; + std::unique_ptr<Formatter> f{Formatter::create_unique("xml", "xml")}; + cct->do_command("config diff get", cmdmap, f.get(), ss, &out); + f->flush(out); + string s(out.c_str(), out.length()); + EXPECT_EQ("<config_diff_get><diff><key><default></default><override>" + value + "</override><final>value</final></key><rbd_default_features><default>61</default><final>61</final></rbd_default_features><rbd_qos_exclude_ops><default>0</default><final>0</final></rbd_qos_exclude_ops></diff></config_diff_get>", s); + } + cct->put(); +} + +TEST(CephContext, experimental_features) +{ + CephContext *cct = (new CephContext(CEPH_ENTITY_TYPE_CLIENT))->get(); + + cct->_conf->cluster = "ceph"; + + ASSERT_FALSE(cct->check_experimental_feature_enabled("foo")); + ASSERT_FALSE(cct->check_experimental_feature_enabled("bar")); + ASSERT_FALSE(cct->check_experimental_feature_enabled("baz")); + + cct->_conf.set_val("enable_experimental_unrecoverable_data_corrupting_features", + "foo,bar"); + cct->_conf.apply_changes(&cout); + ASSERT_TRUE(cct->check_experimental_feature_enabled("foo")); + ASSERT_TRUE(cct->check_experimental_feature_enabled("bar")); + ASSERT_FALSE(cct->check_experimental_feature_enabled("baz")); + + cct->_conf.set_val("enable_experimental_unrecoverable_data_corrupting_features", + "foo bar"); + cct->_conf.apply_changes(&cout); + ASSERT_TRUE(cct->check_experimental_feature_enabled("foo")); + ASSERT_TRUE(cct->check_experimental_feature_enabled("bar")); + ASSERT_FALSE(cct->check_experimental_feature_enabled("baz")); + + cct->_conf.set_val("enable_experimental_unrecoverable_data_corrupting_features", + "baz foo"); + cct->_conf.apply_changes(&cout); + ASSERT_TRUE(cct->check_experimental_feature_enabled("foo")); + ASSERT_FALSE(cct->check_experimental_feature_enabled("bar")); + ASSERT_TRUE(cct->check_experimental_feature_enabled("baz")); + + cct->_conf.set_val("enable_experimental_unrecoverable_data_corrupting_features", + "*"); + cct->_conf.apply_changes(&cout); + ASSERT_TRUE(cct->check_experimental_feature_enabled("foo")); + ASSERT_TRUE(cct->check_experimental_feature_enabled("bar")); + ASSERT_TRUE(cct->check_experimental_feature_enabled("baz")); + + cct->_log->flush(); +} + +/* + * Local Variables: + * compile-command: "cd ../.. ; + * make unittest_context && + * valgrind \ + * --max-stackframe=20000000 --tool=memcheck \ + * ./unittest_context # --gtest_filter=CephContext.* + * " + * End: + */ diff --git a/src/test/common/test_convenience.cc b/src/test/common/test_convenience.cc new file mode 100644 index 000000000..4f4ba386e --- /dev/null +++ b/src/test/common/test_convenience.cc @@ -0,0 +1,69 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2017 Red Hat, Inc. + * + * Author: Casey Bodley <cbodley@redhat.com> + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "common/convenience.h" // include first: tests that header is standalone + +#include <string> +#include <boost/optional.hpp> +#include <gtest/gtest.h> + +// A just god would not allow the C++ standard to make taking the +// address of member functions in the standard library undefined behavior. +static std::string::size_type l(const std::string& s) { + return s.size(); +} + +TEST(Convenience, MaybeDo) +{ + boost::optional<std::string> s("qwerty"); + boost::optional<std::string> t; + auto r = ceph::maybe_do(s, l); + EXPECT_TRUE(r); + EXPECT_EQ(*r, s->size()); + + EXPECT_FALSE(ceph::maybe_do(t, l)); +} + +TEST(Convenience, MaybeDoOr) +{ + const boost::optional<std::string> s("qwerty"); + const boost::optional<std::string> t; + auto r = ceph::maybe_do_or(s, l, 0); + EXPECT_EQ(r, s->size()); + + EXPECT_EQ(ceph::maybe_do_or(t, l, 0u), 0u); +} + +TEST(Convenience, StdMaybeDo) +{ + std::optional<std::string> s("qwerty"); + std::optional<std::string> t; + auto r = ceph::maybe_do(s, l); + EXPECT_TRUE(r); + EXPECT_EQ(*r, s->size()); + + EXPECT_FALSE(ceph::maybe_do(t, l)); +} + +TEST(Convenience, StdMaybeDoOr) +{ + const std::optional<std::string> s("qwerty"); + const std::optional<std::string> t; + auto r = ceph::maybe_do_or(s, l, 0); + EXPECT_EQ(r, s->size()); + + EXPECT_EQ(ceph::maybe_do_or(t, l, 0u), 0u); +} diff --git a/src/test/common/test_counter.cc b/src/test/common/test_counter.cc new file mode 100644 index 000000000..f9a7d6e6c --- /dev/null +++ b/src/test/common/test_counter.cc @@ -0,0 +1,40 @@ +#include "common/DecayCounter.h" + +#include <gtest/gtest.h> + +#include <list> +#include <cmath> + +TEST(DecayCounter, steady) +{ + static const double duration = 2.0; + static const double max = 2048.0; + static const double rate = 3.5; + + DecayCounter d{DecayRate{rate}}; + d.hit(max); + const auto start = DecayCounter::clock::now(); + double total = 0.0; + while (1) { + const auto now = DecayCounter::clock::now(); + auto el = std::chrono::duration<double>(now-start); + if (el.count() > duration) { + break; + } + + double v = d.get(); + double diff = max-v; + if (diff > 0.0) { + d.hit(diff); + total += diff; + } + } + + /* Decay function: dN/dt = -λM where λ = ln(0.5)/rate + * (where M is the maximum value of the counter, not varying with time.) + * Integrating over t: N = -λMt (+c) + */ + double expected = -1*std::log(0.5)/rate*max*duration; + std::cerr << "t " << total << " e " << expected << std::endl; + ASSERT_LT(std::abs(total-expected)/expected, 0.05); +} diff --git a/src/test/common/test_crc32c.cc b/src/test/common/test_crc32c.cc new file mode 100644 index 000000000..ff5d0019d --- /dev/null +++ b/src/test/common/test_crc32c.cc @@ -0,0 +1,365 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include <iostream> +#include <string.h> + +#include "include/types.h" +#include "include/crc32c.h" +#include "include/utime.h" +#include "common/Clock.h" + +#include "gtest/gtest.h" + +#include "common/sctp_crc32.h" +#include "common/crc32c_intel_baseline.h" +#include "common/crc32c_aarch64.h" + +TEST(Crc32c, Small) { + const char *a = "foo bar baz"; + const char *b = "whiz bang boom"; + ASSERT_EQ(4119623852u, ceph_crc32c(0, (unsigned char *)a, strlen(a))); + ASSERT_EQ(881700046u, ceph_crc32c(1234, (unsigned char *)a, strlen(a))); + ASSERT_EQ(2360230088u, ceph_crc32c(0, (unsigned char *)b, strlen(b))); + ASSERT_EQ(3743019208u, ceph_crc32c(5678, (unsigned char *)b, strlen(b))); +} + +TEST(Crc32c, PartialWord) { + const char *a = (const char *)malloc(5); + const char *b = (const char *)malloc(35); + memset((void *)a, 1, 5); + memset((void *)b, 1, 35); + ASSERT_EQ(2715569182u, ceph_crc32c(0, (unsigned char *)a, 5)); + ASSERT_EQ(440531800u, ceph_crc32c(0, (unsigned char *)b, 35)); + free((void*)a); + free((void*)b); +} + +TEST(Crc32c, Big) { + int len = 4096000; + char *a = (char *)malloc(len); + memset(a, 1, len); + ASSERT_EQ(31583199u, ceph_crc32c(0, (unsigned char *)a, len)); + ASSERT_EQ(1400919119u, ceph_crc32c(1234, (unsigned char *)a, len)); + free(a); +} + +TEST(Crc32c, Performance) { + int len = 1000 * 1024 * 1024; + char *a = (char *)malloc(len); + std::cout << "populating large buffer" << std::endl; + for (int i=0; i<len; i++) + a[i] = i & 0xff; + std::cout << "calculating crc" << std::endl; + + { + utime_t start = ceph_clock_now(); + unsigned val = ceph_crc32c(0, (unsigned char *)a, len); + utime_t end = ceph_clock_now(); + float rate = (float)len / (float)(1024*1024) / (float)(end - start); + std::cout << "best choice = " << rate << " MB/sec" << std::endl; + ASSERT_EQ(261108528u, val); + } + { + utime_t start = ceph_clock_now(); + unsigned val = ceph_crc32c(0xffffffff, (unsigned char *)a, len); + utime_t end = ceph_clock_now(); + float rate = (float)len / (float)(1024*1024) / (float)(end - start); + std::cout << "best choice 0xffffffff = " << rate << " MB/sec" << std::endl; + ASSERT_EQ(3895876243u, val); + } + { + utime_t start = ceph_clock_now(); + unsigned val = ceph_crc32c_sctp(0, (unsigned char *)a, len); + utime_t end = ceph_clock_now(); + float rate = (float)len / (float)(1024*1024) / (float)(end - start); + std::cout << "sctp = " << rate << " MB/sec" << std::endl; + ASSERT_EQ(261108528u, val); + } + { + utime_t start = ceph_clock_now(); + unsigned val = ceph_crc32c_intel_baseline(0, (unsigned char *)a, len); + utime_t end = ceph_clock_now(); + float rate = (float)len / (float)(1024*1024) / (float)(end - start); + std::cout << "intel baseline = " << rate << " MB/sec" << std::endl; + ASSERT_EQ(261108528u, val); + } +#if defined(__arm__) || defined(__aarch64__) + if (ceph_arch_aarch64_crc32) // Skip if CRC32C instructions are not defined. + { + utime_t start = ceph_clock_now(); + unsigned val = ceph_crc32c_aarch64(0, (unsigned char *)a, len); + utime_t end = ceph_clock_now(); + float rate = (float)len / (float)(1024*1024) / (float)(end - start); + std::cout << "aarch64 = " << rate << " MB/sec" << std::endl; + ASSERT_EQ(261108528u, val); + } +#endif + free(a); +} + + +static uint32_t crc_check_table[] = { +0xcfc75c75, 0x7aa1b1a7, 0xd761a4fe, 0xd699eeb6, 0x2a136fff, 0x9782190d, 0xb5017bb0, 0xcffb76a9, +0xc79d0831, 0x4a5da87e, 0x76fb520c, 0x9e19163d, 0xe8eacd22, 0xefd4319e, 0x1eaa804b, 0x7ff41ccb, +0x94141dab, 0xb4c2588f, 0x484bf16f, 0x77725048, 0xf27d43ee, 0x3604f655, 0x20bb9b79, 0xd6ee30ba, +0xf402f02d, 0x59992eec, 0x159c0449, 0xe2d72e60, 0xc519c744, 0xf56f7995, 0x7e40be36, 0x695ccedc, +0xc95c4ae3, 0xb0d2d6bc, 0x85872e14, 0xea2c01b0, 0xe9b75f1a, 0xebb23ae3, 0x39faee13, 0x313cb413, +0xe683eb7d, 0xd22e2ae1, 0xf49731dd, 0x897a8e60, 0x923b510e, 0xe0e0f3b, 0x357dd0f, 0x63b7aa7d, +0x6f5c2a40, 0x46b09a37, 0x80324751, 0x380fd024, 0x78b122c6, 0xb29d1dde, 0x22f19ddc, 0x9d6ee6d6, +0xfb4e7e1c, 0xb9780044, 0x85feef90, 0x8e4fae11, 0x1a71394a, 0xbe21c888, 0xde2f6f47, 0x93c365f0, +0xfd1d3814, 0x6e0a23df, 0xc6739c17, 0x2d48520d, 0x3357e475, 0x5d57058a, 0x22c4b9f7, 0x5a498b58, +0x7bed8ddb, 0xcf1eb035, 0x2094f389, 0xb6a7c977, 0x289d29e2, 0x498d5b7, 0x8db77420, 0x85300608, +0x5d1c04c4, 0x5acfee62, 0x99ad4694, 0x799f9833, 0x50e76ce1, 0x72dc498, 0x70a393be, 0x905a364d, +0x1af66b95, 0x5b3eed9e, 0xa3e4da14, 0xc720fece, 0x555200df, 0x169fd3e0, 0x531c18c0, 0x6f9b6092, +0x6d16638b, 0x5a8c8b6a, 0x818ebab2, 0xd75b10bb, 0xcaa01bfa, 0x67377804, 0xf8a085ae, 0xfc7d88b8, +0x5e2debc1, 0x9759cb1f, 0x24c39b63, 0x210afbba, 0x22f7c6f7, 0xa8f8dc11, 0xf1d4550c, 0x1d2b1e47, +0x59a44605, 0x25402e97, 0x18401ea, 0xb1884203, 0xd6ef715, 0x1797b686, 0x9e7f5aa7, 0x30795e88, +0xb280b636, 0x77258b7d, 0x5f8dbff3, 0xbb57ea03, 0xa2c35cce, 0x1acce538, 0xa50be97a, 0x417f4b57, +0x6d94792f, 0x4bb6fb34, 0x3787440c, 0x9a77b0b9, 0x67ece3d0, 0x5a8450fe, 0x8e66f55b, 0x3cefce93, +0xf7ca60ab, 0xce7cd3b7, 0x97976493, 0xa05632f8, 0x77ac4546, 0xed24c705, 0x92a2f20, 0xc0b1cc9, +0x831ae4e1, 0x5b3f28b1, 0xee6fca02, 0x74acc743, 0xaf40043f, 0x5f21e837, 0x9e168fc0, 0x64e28de, +0x88ae891d, 0xac2e4ff5, 0xaeaf9c27, 0x158a2d3, 0x5226fb01, 0x9bf56ae1, 0xe4a2dd8d, 0x2599d6de, +0xe798b5ee, 0x39efe57a, 0xbb9965c7, 0x4516fde0, 0xa41831f5, 0xd7cd0797, 0xd07b7d5c, 0xb330d048, +0x3a47e35d, 0x87dd39e5, 0xa806fb31, 0xad228dd, 0xcc390816, 0x9237a4de, 0x8dfe1c20, 0x304f6bc, +0x3ad98572, 0xec13f349, 0x4e5278d7, 0x784c4bf4, 0x7b93cb23, 0xa18c87ae, 0x84ff79dd, 0x8e95061d, +0xd972f4d4, 0x4ad50380, 0x23cbc187, 0x7fa7f22c, 0x6062c18e, 0x42381901, 0x10cf51d9, 0x674e22a4, +0x28a63445, 0x6fc1b591, 0xa4dc117a, 0x744a00d0, 0x8a5470ea, 0x9539c6a7, 0xc961a584, 0x22f81498, +0xae299e51, 0x5653fcd3, 0x7bfa474f, 0x7f502c42, 0xfb41c744, 0xd478fb95, 0x7b676978, 0xb22f5610, +0xbcbe730c, 0x70ff5773, 0xde990b63, 0xebcbf9d5, 0x2d029133, 0xf39513e1, 0x56229640, 0x660529e5, +0x3b90bdf8, 0xc9822978, 0x4e3daab1, 0x2e43ce72, 0x572bb6ff, 0xdc4b17bd, 0x6c290d46, 0x7d9644ca, +0x7652fd89, 0x66d72059, 0x521e93d4, 0xd626ff95, 0xdc4eb57e, 0xb0b3307c, 0x409adbed, 0x49ae2d28, +0x8edd249a, 0x8e4fb6ec, 0x5a191fbf, 0xe1751948, 0xb4ae5d00, 0xabeb1bdd, 0xbe204b60, 0xbc97aad4, +0xb8cb5915, 0x54f33261, 0xc5d83b28, 0x99d0d099, 0xfb06f8b2, 0x57305f66, 0xf9fde17b, 0x192f143c, +0xcc3c58fd, 0x36e2e420, 0x17118208, 0xcac7e42a, 0xb45ad63d, 0x8ad5e475, 0xb7a3bc1e, 0xe03e64ad, +0x2c197d77, 0x1a0ff1fe, 0xbcd443fb, 0x7589393a, 0xd66b1f67, 0xdddf0a66, 0x4750b7c7, 0xc62a79db, +0xcf02a0d3, 0xb4012205, 0x9733d16c, 0x9a29cff8, 0xdd3d6427, 0x15c0273a, 0x97b289b, 0x358ff573, +0x73a9ceb7, 0xc3788b1a, 0xda7a5155, 0x2990a31, 0x9fa4705, 0x5eb4e2e2, 0x98465bb2, 0x74a17883, +0xe87df542, 0xe20f22f1, 0x48ffd67e, 0xc94fab5f, 0x9eb431d2, 0xffd673cb, 0xc374dc18, 0xa542fbf7, +0xb8fea538, 0x43f5431f, 0xcbe3fb7d, 0x2734e0e4, 0x5cb05a8, 0xd00fcf47, 0x248dbbae, 0x47d4de6c, +0xecc97151, 0xca8c379b, 0x49049fd, 0xeb2acd18, 0xab178ac, 0xc98ab95d, 0xb9e0be20, 0x36664a13, +0x95d81459, 0xb54973a9, 0x27f9579c, 0xa24fb6df, 0x3f6f8cea, 0xe11efdd7, 0x68166281, 0x586e0a6, +0x5fad7b57, 0xd58f50ad, 0x6e0d3be8, 0x27a00831, 0x543b3761, 0x96c862fb, 0xa823ed4f, 0xf6043f37, +0x980703eb, 0xf5e69514, 0x42a2082, 0x495732a2, 0x793eea23, 0x6a6a17fb, 0x77d75dc5, 0xb3320ec4, +0x10d4d01e, 0xa17508a6, 0x6d578355, 0xd136c445, 0xafa6acc6, 0x2307831d, 0x5bf345fd, 0xb9a04582, +0x2627a686, 0xf6f4ce3b, 0xd0ac868f, 0x78d6bdb3, 0xfe42945a, 0x8b06cbf3, 0x2b169628, 0xf072b8b7, +0x8652a0ca, 0x3f52fc42, 0xa0415b9a, 0x16e99341, 0x7394e9c7, 0xac92956c, 0x7bff7137, 0xb0e8ea5c, +0x42d8c22, 0x4318a18, 0x42097180, 0x57d17dba, 0xb1f7a567, 0x55186d60, 0xf527e0ca, 0xd58b0b48, +0x31d9155b, 0xd5fd0441, 0x6024d751, 0xe14d03c3, 0xba032e1c, 0xd6d89ae7, 0x54f1967a, 0xe401c200, +0x8ee973ff, 0x3d24277e, 0xab394cbf, 0xe3b39762, 0x87f43766, 0xe4c2bdff, 0x1234c0d7, 0x8ef3e1bd, +0xeeb00f61, 0x15d17d4b, 0x7d40ac8d, 0xada8606f, 0x7ba5e3a1, 0xcf487cf9, 0x98dda708, 0x6d7c9bea, +0xaecb321c, 0x9f7801b2, 0x53340341, 0x7ae27355, 0xbf859829, 0xa36a00b, 0x99339435, 0x8342d1e, +0x4ab4d7ea, 0x862d01cd, 0x7f94fbee, 0xe329a5a3, 0x2cb7ba81, 0x50bae57a, 0x5bbd65cf, 0xf06f60e4, +0x569ad444, 0xfa0c16c, 0xb8c2b472, 0x3ea64ea1, 0xc6dc4c18, 0x5d6d654a, 0x5369a931, 0x2163bf7f, +0xe45bd590, 0xcc826d18, 0xb4ce22f6, 0x200f7232, 0x5f2f869c, 0xffd5cc17, 0x1a578942, 0x930da3ea, +0x216377f, 0x9f07a04b, 0x1f2a777c, 0x13c95089, 0x8a64d032, 0x1eecb206, 0xc537dc4, 0x319f9ac8, +0xe2131194, 0x25d2f716, 0xa27f471a, 0xf6434ce2, 0xd51a10b9, 0x4e28a61, 0x647c888a, 0xb383d2ff, +0x93aa0d0d, 0x670d1317, 0x607f36e2, 0x73e01833, 0x2bd372b0, 0x86404ad2, 0x253d5cc4, 0x1348811c, +0x8756f2d5, 0xe1e55a59, 0x5247e2d1, 0x798ab6b, 0x181bbc57, 0xb9ea36e0, 0x66081c68, 0x9bf0bad7, +0x892b1a6, 0x8a6a9aed, 0xda955d0d, 0x170e5128, 0x81733d84, 0x6d9f6b10, 0xd60046fd, 0x7e401823, +0xf9904ce6, 0xaa765665, 0x2fd5c4ee, 0xbb9c1580, 0x391dac53, 0xbffe4270, 0x866c30b1, 0xd629f22, +0x1ee5bfee, 0x5af91c96, 0x96b613bf, 0xa65204c9, 0x9b8cb68c, 0xd08b37c1, 0xf1863f8f, 0x1e4c844a, +0x876abd30, 0x70c07eff, 0x63d8e875, 0x74351f92, 0xffe7712d, 0x58c0171d, 0x7b826b99, 0xc09afc78, +0xd81d3065, 0xccced8b1, 0xe258b1c9, 0x5659d6b, 0x1959c406, 0x53bd05e6, 0xa32f784b, 0x33351e4b, +0xb6b9d769, 0x59e5802c, 0x118c7ff7, 0x46326e0b, 0xa7376fbe, 0x7218aed1, 0x28c8f707, 0x44610a2f, +0xf8eafea1, 0xfe36fdae, 0xb4b546f1, 0x2e27ce89, 0xc1fde8a0, 0x99f2f157, 0xfde687a1, 0x40a75f50, +0x6c653330, 0xf3e38821, 0xf4663e43, 0x2f7e801e, 0xfca360af, 0x53cd3c59, 0xd20da292, 0x812a0241 }; + +TEST(Crc32c, Range) { + int len = sizeof(crc_check_table) / sizeof(crc_check_table[0]); + unsigned char *b = (unsigned char *)malloc(len); + memset(b, 1, len); + uint32_t crc = 0; + uint32_t *check = crc_check_table; + for (int i = 0 ; i < len; i++, check++) { + crc = ceph_crc32c(crc, b+i, len-i); + ASSERT_EQ(crc, *check); + } + free(b); +} + +static uint32_t crc_zero_check_table[] = { +0xbd6f81f8, 0x6213374d, 0x72952aeb, 0x8ecb5e52, 0xa04914b4, 0xaf3aaea9, 0xb88d42d6, 0x81797724, +0xc0022634, 0x4dbf46a4, 0xc7813aa, 0x172150e0, 0x13d8d958, 0x339fd933, 0xd9e725f4, 0x20b65b14, +0x349c971c, 0x7f812818, 0x5228e357, 0x811f231f, 0xe4bdaeee, 0xcdd22442, 0x26ae3c58, 0xf9628c5e, +0x8118e80b, 0xca0ea635, 0xc5028f6d, 0xbd2270, 0x4d9171a3, 0xe810af42, 0x904c7218, 0xdc62c735, +0x3c8b3748, 0x7cae4eef, 0xed170242, 0xdc0a6a28, 0x4afb0591, 0x4643748a, 0xad28d5b, 0xeb2d60d3, +0x479d21a9, 0x2a0916c1, 0x144cd9fb, 0x2498ba7a, 0x196489f, 0x330bb594, 0x5abe491d, 0x195658fe, +0xc6ef898f, 0x94b251a1, 0x4f968332, 0xfbf5f29d, 0x7b4828ce, 0x3af20a6f, 0x653a721f, 0x6d92d018, +0xf43ca065, 0xf55da16e, 0x94af47c6, 0xf08abdc, 0x11344631, 0xb249e575, 0x1f9f992b, 0xfdb6f490, +0xbd40d84b, 0x945c69e1, 0x2a94e2e3, 0xe5aa9b91, 0x89cebb57, 0x175a3097, 0x502b7d34, 0x174f2c92, +0x2a8f01c0, 0x645a2db8, 0x9e9a4a8, 0x13adac02, 0x2759a24b, 0x8bfcb972, 0xfa1edbfe, 0x5a88365e, +0x5c107fd9, 0x91ac73a8, 0xbd40e99e, 0x513011ca, 0x97bd2841, 0x336c1c4e, 0x4e88563e, 0x6948813e, +0x96e1cbee, 0x64b2faa5, 0x9671e44, 0x7d492fcb, 0x3539d74a, 0xcbe26ad7, 0x6106e673, 0x162115d, +0x8534e6a6, 0xd28a1ea0, 0xf73beb20, 0x481bdbae, 0xcd12e442, 0x8ab52843, 0x171d72c4, 0xd97cb216, +0x60fa0ecf, 0x74336ebb, 0x4d67fd86, 0x9393e96a, 0x63670234, 0x3f2a31da, 0x4036c11f, 0x55cc2ceb, +0xf75b27dc, 0xcabdca83, 0x80699d1a, 0x228c13a1, 0x5ea7f8a9, 0xc7631f40, 0x710b867a, 0xaa6e67b9, +0x27444987, 0xd693cd2a, 0xc4e21e0c, 0xd340e1cb, 0x2a2a346f, 0xac55e843, 0xfcd2750c, 0x4529a016, +0x7ac5802, 0xa2eb291f, 0x4a0fb9ea, 0x6a58a9a0, 0x51f56797, 0xda595134, 0x267aba96, 0x8ba80ee, +0x4474659e, 0x2b7bacb, 0xba524d37, 0xb60981bb, 0x5fd43811, 0xca41594a, 0x98ace58, 0x3fc5b984, +0x6a290b91, 0x6576108a, 0x8c33c85e, 0x52622407, 0x99cf8723, 0x68198dc8, 0x18b7341d, 0x540fc0f9, +0xf4a7b6f6, 0xfade9dfa, 0x725471ca, 0x5c160723, 0x5f33b243, 0xecec5d09, 0x6f520abb, 0x139c7bca, +0x58349acb, 0x1fccef32, 0x1d01aa0f, 0x3f477a65, 0xebf55472, 0xde9ae082, 0x76d3119e, 0x937e2708, +0xba565506, 0xbe820951, 0xc1f336fa, 0xfc41afb6, 0x4ef12d88, 0xd6f6d4f, 0xb33fb3fe, 0x9c6d1ae, +0x24ae1c29, 0xf9ae57f7, 0x51d1e4c9, 0x86dc73fc, 0x54b7bf38, 0x688a141c, 0x91d4ea7a, 0xd57a0fd0, +0x5cdcd16f, 0xc59c135a, 0x5bb003b5, 0x730b52f3, 0xc1dc5b1e, 0xf083f53, 0x8159e7c8, 0xf396d2e3, +0x1c7f18ec, 0x5bedc75e, 0x2f11fbfd, 0xb4437094, 0x77c55e3, 0x1d8636e1, 0x159bf2f, 0x6cbabf5b, +0xf4d005bc, 0x39f0bc55, 0x3d525f54, 0x8422e29d, 0xfb8a413d, 0x66e78593, 0xa0e14663, 0x880b8fa1, +0x24b53713, 0x12105ff3, 0xa94dd90f, 0x3ff981bc, 0xaf2366af, 0x8e98710, 0x48eb45c6, 0xbc3aee53, +0x6933d852, 0xe236cfd3, 0x3e6c50af, 0xe309e3fd, 0x452eac88, 0x725bf633, 0xbe89339a, 0x4b54eff7, +0xa57e392f, 0x6ee15bef, 0x67630f96, 0x31656c71, 0x77fc97f0, 0x1d29682f, 0xa4b0fc5d, 0xb3fd0ee1, +0x9d10aa57, 0xf104e21, 0x478b5f75, 0xaf1ca64b, 0x13e8a297, 0x21caa105, 0xb3cb8e9d, 0xd4536cb, +0x425bdfce, 0x90462d05, 0x8cace1cf, 0xc0ab7293, 0xbcf288cb, 0x5edcdc11, 0x4ec8b5e0, 0x42738654, +0x4ba49663, 0x2b264337, 0x41d1a5ce, 0xaa8acb92, 0xe79714aa, 0x86695e7c, 0x1330c69a, 0xe0c6485f, +0xb038b81a, 0x6f823a85, 0x4eeff0e4, 0x7355d58f, 0x7cc87e83, 0xe23e4619, 0x7093faa0, 0x7328cb2f, +0x7856db5e, 0xbc38d892, 0x1e4307c8, 0x347997e1, 0xb26958, 0x997ddf1e, 0x58dc72e3, 0x4b6e9a77, +0x49eb9924, 0x36d555db, 0x59456efd, 0x904bd6d2, 0xd932837d, 0xf96a24ec, 0x525aa449, 0x5fd05bc7, +0x84778138, 0xd869bfe1, 0xe6bbd546, 0x2f796af4, 0xbaab980f, 0x7f18a176, 0x3a8e00d9, 0xb589ea81, +0x77920ee3, 0xc6730dbc, 0x8a5df534, 0xb7df9a12, 0xdc93009c, 0x215b885, 0x309104b, 0xf47e380b, +0x23f6cdef, 0xe112a923, 0x83686f38, 0xde2c7871, 0x9f728ec7, 0xeaae7af6, 0x6d7b7b0a, 0xaf0cde04, +0xfcb51a1f, 0xf0cd53cf, 0x7aa5556a, 0xa64ccf7e, 0x854c2084, 0xc493ddd4, 0x92684099, 0x913beb92, +0xe4067ea8, 0x9557605a, 0x934346d6, 0x23a3a7c7, 0x588b2805, 0xe1e755ae, 0xe4c05e84, 0x8e09d0f3, +0x1343a510, 0x6175c2c3, 0x39bb7947, 0x4a1b9b6b, 0xf0e373da, 0xe7b9a201, 0x24b7a392, 0x91a27584, +0x9ac3a10f, 0x91fc9314, 0xc495d878, 0x3fcbc776, 0x7f81d6da, 0x973edb2f, 0xa9d731c6, 0x2dc022a8, +0xa066c881, 0x7e082dff, 0xa1ff394d, 0x1cb0c2bb, 0xef87a116, 0x5179810b, 0xa1594c92, 0xe291e155, +0x3578c98f, 0xb801f82c, 0xa1778ad9, 0xbdd48b76, 0x74f1ce54, 0x46b8de63, 0x3861112c, 0x46a8920f, +0x3e1075e7, 0x220a49dd, 0x3e51d6d2, 0xbf1f22cd, 0x5d1490c5, 0x7f1e05f5, 0xa0c1691d, 0x9108debf, +0xe69899b, 0xe771d8b6, 0x878c92c1, 0x973e37c0, 0x833c4c25, 0xcffe7b03, 0x92e0921e, 0xccee9836, +0xa9739832, 0xc774f2f2, 0xf34f9467, 0x608cef83, 0x97a584d2, 0xf5218c9, 0x73eb9524, 0xb3fb4870, +0x53296e3d, 0x8836f46f, 0x9d6a40b0, 0x789b5e91, 0x62a915ba, 0x32c02d74, 0xc93de2f3, 0xefa67fc7, +0x169ee4f1, 0x72bbbe9e, 0x49357cf2, 0x219207bf, 0x12516225, 0x182df160, 0x230c9a3f, 0x137a8497, +0xa429ad30, 0x4aa66f88, 0x40319931, 0xfa241c42, 0x1e5189ec, 0xca693ada, 0xe7b923f4, 0xff546a06, +0xf01103c2, 0x99875a32, 0x4bbf55a9, 0x48abdf3e, 0x85eb3dec, 0x2d009057, 0x14c2a682, 0xfabe68af, +0x96a31fa6, 0xf52f4686, 0x73f72b61, 0x92f39e13, 0x66794863, 0x7ca4c2aa, 0x37a2fe39, 0x33be288a, +0x1ff9a59c, 0xd65e667, 0x5d7c9332, 0x8a6a2d8b, 0x37ec2d3b, 0x9f935ab9, 0x67fcd589, 0x48a09508, +0xc446e984, 0x58f69202, 0x968dfbbb, 0xc93d7626, 0x82344e, 0xf1d930a4, 0xcc3acdde, 0x20cf92bf, +0x94b7616d, 0xb0e45050, 0xdc36c072, 0x74cba0, 0x6478300a, 0x27803b97, 0xb7b2ebd0, 0xb3a691e, +0x35c2f261, 0x3fcff45a, 0x3e4b7b93, 0x86b680bd, 0x720333ce, 0x67f933ca, 0xb10256de, 0xe939bb3f, +0xb540a02f, 0x39a8b8e4, 0xb6a63aa5, 0x5e1d56ee, 0xa415a16, 0xcb5753d, 0x17fabd19, 0x90eac10d, +0x2308857d, 0xb8f6224c, 0x71790390, 0x18749d48, 0xed778f1b, 0x69f0e17c, 0xbd622f4, 0x52c3a79e, +0x9697bf51, 0xa768755c, 0x9fe860ea, 0xa852b0ac, 0x9549ec64, 0x8669c603, 0x120e289c, 0x3f0520f5, +0x9b15884, 0x2d06fa7f, 0x767b12f6, 0xcb232dd6, 0x4e2b4590, 0x97821835, 0x4506a582, 0xd974dbaa, +0x379bd22f, 0xb9d65a2f, 0x8fad14d9, 0x72a55b5f, 0x34d56c6e, 0xc0badd55, 0xc20ee31b, 0xeb567f69, +0xdadac1c, 0xb6dcc8f5, 0xc6d89117, 0x16c4999d, 0xc9b0da2a, 0xfcd6e9b3, 0x72d299ae, 0x4c2b345b, +0x5d2c06cb, 0x9b9a3ce2, 0x8e84866, 0x876d1806, 0xbaeb6183, 0xe2a89d5d, 0x4604d2fe, 0x9909c5e0, +0xf2fb7bec, 0x7e04dcd0, 0xe5b24865, 0xda96b760, 0x74a4d01, 0xb0f35bea, 0x9a2edb2, 0x5327a0d3 }; + + +TEST(Crc32c, RangeZero) { + int len = sizeof(crc_zero_check_table) / sizeof(crc_zero_check_table[0]); + unsigned char *b = (unsigned char *)malloc(len); + memset(b, 0, len); + uint32_t crc = 1; /* when checking zero buffer we want to start with a non zero crc, otherwise + all the results are going to be zero */ + uint32_t *check = crc_zero_check_table; + for (int i = 0 ; i < len; i++, check++) { + crc = ceph_crc32c(crc, b+i, len-i); + ASSERT_EQ(crc, *check); + } + free(b); +} + +TEST(Crc32c, RangeNull) { + int len = sizeof(crc_zero_check_table) / sizeof(crc_zero_check_table[0]); + uint32_t crc = 1; /* when checking zero buffer we want to start with a non zero crc, otherwise + all the results are going to be zero */ + uint32_t *check = crc_zero_check_table; + + for (int i = 0 ; i < len; i++, check++) { + crc = ceph_crc32c(crc, NULL, len-i); + ASSERT_EQ(crc, *check); + } +} + +double estimate_clock_resolution() +{ + volatile char* p = (volatile char*)malloc(1024); + utime_t start; + utime_t end; + std::set<double> S; + for(int j=10; j<200; j+=1) { + start = ceph_clock_now(); + for (int i=0; i<j; i++) + p[i]=1; + end = ceph_clock_now(); + S.insert((double)(end - start)); + } + auto head = S.begin(); + auto tail = S.end(); + for (size_t i=0; i<S.size()/4; i++) { + ++head; + --tail; + } + double v = *(head++); + double range=0; + while (head != tail) { + range = std::max(range, *head - v); + v = *head; + head++; + } + free((void*)p); + return range; +} + +TEST(Crc32c, zeros_performance_compare) { + double resolution = estimate_clock_resolution(); + utime_t start; + utime_t pre_start; + utime_t end; + double time_adjusted; + using namespace std::chrono; + high_resolution_clock::now(); + for (size_t scale=1; scale < 31; scale++) + { + size_t size = (1<<scale) + rand()%(1<<scale); + pre_start = ceph_clock_now(); + start = ceph_clock_now(); + uint32_t crc_a = ceph_crc32c(111, nullptr, size); + end = ceph_clock_now(); + time_adjusted = (end - start) - (start - pre_start); + std::cout << "regular method. size=" << size << " time= " << (double)(end-start) + << " at " << (double)size/(1024*1024)/(time_adjusted) << " MB/sec" + << " error=" << resolution / time_adjusted * 100 << "%" << std::endl; + + pre_start = ceph_clock_now(); + start = ceph_clock_now(); +#ifdef HAVE_POWER8 + uint32_t crc_b = ceph_crc32c_zeros(111, size); +#else + uint32_t crc_b = ceph_crc32c_func(111, nullptr, size); +#endif + end = ceph_clock_now(); + time_adjusted = (end - start) - (start - pre_start); +#ifdef HAVE_POWER8 + std::cout << "ceph_crc32c_zeros method. size=" << size << " time=" + << (double)(end-start) << " at " << (double)size/(1024*1024)/(time_adjusted) + << " MB/sec" << " error=" << resolution / time_adjusted * 100 << "%" + << std::endl; +#else + std::cout << "fallback method. size=" << size << " time=" << (double)(end-start) + << " at " << (double)size/(1024*1024)/(time_adjusted) << " MB/sec" + << " error=" << resolution / time_adjusted * 100 << "%" << std::endl; +#endif + EXPECT_EQ(crc_a, crc_b); + } +} + +TEST(Crc32c, zeros_performance) { + constexpr size_t ITER=100000; + utime_t start; + utime_t end; + + start = ceph_clock_now(); + for (size_t i=0; i<ITER; i++) + { + for (size_t scale=1; scale < 31; scale++) + { + size_t size = (1<<scale) + rand() % (1<<scale); + ceph_crc32c(rand(), nullptr, size); + } + } + end = ceph_clock_now(); + std::cout << "iterations="<< ITER*31 << " time=" << (double)(end-start) << std::endl; + +} + diff --git a/src/test/common/test_fair_mutex.cc b/src/test/common/test_fair_mutex.cc new file mode 100644 index 000000000..10ba835a2 --- /dev/null +++ b/src/test/common/test_fair_mutex.cc @@ -0,0 +1,68 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- + +#include <array> +#include <mutex> +#include <numeric> +#include <future> +#include <gtest/gtest.h> +#include "common/fair_mutex.h" + +TEST(FairMutex, simple) +{ + ceph::fair_mutex mutex{"fair::simple"}; + { + std::unique_lock lock{mutex}; + ASSERT_TRUE(mutex.is_locked()); + // fair_mutex does not recursive ownership semantics + ASSERT_FALSE(mutex.try_lock()); + } + // re-acquire the lock + { + std::unique_lock lock{mutex}; + ASSERT_TRUE(mutex.is_locked()); + } + ASSERT_FALSE(mutex.is_locked()); +} + +TEST(FairMutex, fair) +{ + // waiters are queued in FIFO order, and they are woken up in the same order + // we have a marathon participated by multiple teams: + // - each team is represented by a thread. + // - each team should have equal chance of being selected and scoring, assuming + // the runners in each team are distributed evenly in the waiting queue. + ceph::fair_mutex mutex{"fair::fair"}; + const int NR_TEAMS = 2; + std::array<unsigned, NR_TEAMS> scoreboard{0, 0}; + const int NR_ROUNDS = 512; + auto play = [&](int team) { + for (int i = 0; i < NR_ROUNDS; i++) { + std::unique_lock lock{mutex}; + // pretent that i am running.. and it takes time + std::this_thread::sleep_for(std::chrono::microseconds(20)); + // score! + scoreboard[team]++; + // fair? + unsigned total = std::accumulate(scoreboard.begin(), + scoreboard.end(), + 0); + for (unsigned score : scoreboard) { + if (total < NR_ROUNDS) { + // not quite statistically significant. to reduce the false positive, + // just consider it fair + continue; + } + // check if any team is donimating the game. + unsigned avg = total / scoreboard.size(); + // leave at least half of the average to other teams + ASSERT_LE(score, total - avg / 2); + // don't treat myself too bad + ASSERT_GT(score, avg / 2); + }; + } + }; + std::array<std::future<void>, NR_TEAMS> completed; + for (int team = 0; team < NR_TEAMS; team++) { + completed[team] = std::async(std::launch::async, play, team); + } +} diff --git a/src/test/common/test_fault_injector.cc b/src/test/common/test_fault_injector.cc new file mode 100644 index 000000000..dfa147478 --- /dev/null +++ b/src/test/common/test_fault_injector.cc @@ -0,0 +1,248 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat, Inc. + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "common/fault_injector.h" +#include "common/common_init.h" +#include "common/ceph_argparse.h" +#include <gtest/gtest.h> + +TEST(FaultInjectorDeathTest, InjectAbort) +{ + constexpr FaultInjector f{false, InjectAbort{}}; + EXPECT_EQ(f.check(true), 0); + EXPECT_DEATH([[maybe_unused]] int r = f.check(false), "FaultInjector"); +} + +TEST(FaultInjectorDeathTest, AssignAbort) +{ + FaultInjector<bool> f; + ASSERT_EQ(f.check(false), 0); + f.inject(false, InjectAbort{}); + EXPECT_DEATH([[maybe_unused]] int r = f.check(false), "FaultInjector"); +} + +// death tests have to run in single-threaded mode, so we can't initialize a +// CephContext until after those have run (gtest automatically runs them first) +class Fixture : public testing::Test { + boost::intrusive_ptr<CephContext> cct; + std::optional<NoDoutPrefix> prefix; + protected: + void SetUp() override { + CephInitParameters params(CEPH_ENTITY_TYPE_CLIENT); + cct = common_preinit(params, CODE_ENVIRONMENT_UTILITY, + CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); + prefix.emplace(cct.get(), ceph_subsys_context); + } + void TearDown() override { + prefix.reset(); + cct.reset(); + } + const DoutPrefixProvider* dpp() { return &*prefix; } +}; + +// test int as a Key type +using FaultInjectorInt = Fixture; + +TEST_F(FaultInjectorInt, Default) +{ + constexpr FaultInjector<int> f; + EXPECT_EQ(f.check(0), 0); + EXPECT_EQ(f.check(1), 0); + EXPECT_EQ(f.check(2), 0); + EXPECT_EQ(f.check(3), 0); +} + +TEST_F(FaultInjectorInt, InjectError) +{ + constexpr FaultInjector f{2, InjectError{-EINVAL}}; + EXPECT_EQ(f.check(0), 0); + EXPECT_EQ(f.check(1), 0); + EXPECT_EQ(f.check(2), -EINVAL); + EXPECT_EQ(f.check(3), 0); +} + +TEST_F(FaultInjectorInt, InjectErrorMessage) +{ + FaultInjector f{2, InjectError{-EINVAL, dpp()}}; + EXPECT_EQ(f.check(0), 0); + EXPECT_EQ(f.check(1), 0); + EXPECT_EQ(f.check(2), -EINVAL); + EXPECT_EQ(f.check(3), 0); +} + +TEST_F(FaultInjectorInt, AssignError) +{ + FaultInjector<int> f; + ASSERT_EQ(f.check(0), 0); + f.inject(0, InjectError{-EINVAL}); + EXPECT_EQ(f.check(0), -EINVAL); +} + +TEST_F(FaultInjectorInt, AssignErrorMessage) +{ + FaultInjector<int> f; + ASSERT_EQ(f.check(0), 0); + f.inject(0, InjectError{-EINVAL, dpp()}); + EXPECT_EQ(f.check(0), -EINVAL); +} + +// test std::string_view as a Key type +using FaultInjectorString = Fixture; + +TEST_F(FaultInjectorString, Default) +{ + constexpr FaultInjector<std::string_view> f; + EXPECT_EQ(f.check("Red"), 0); + EXPECT_EQ(f.check("Green"), 0); + EXPECT_EQ(f.check("Blue"), 0); +} + +TEST_F(FaultInjectorString, InjectError) +{ + FaultInjector<std::string_view> f{"Red", InjectError{-EIO}}; + EXPECT_EQ(f.check("Red"), -EIO); + EXPECT_EQ(f.check("Green"), 0); + EXPECT_EQ(f.check("Blue"), 0); +} + +TEST_F(FaultInjectorString, InjectErrorMessage) +{ + FaultInjector<std::string_view> f{"Red", InjectError{-EIO, dpp()}}; + EXPECT_EQ(f.check("Red"), -EIO); + EXPECT_EQ(f.check("Green"), 0); + EXPECT_EQ(f.check("Blue"), 0); +} + +TEST_F(FaultInjectorString, AssignError) +{ + FaultInjector<std::string_view> f; + ASSERT_EQ(f.check("Red"), 0); + f.inject("Red", InjectError{-EINVAL}); + EXPECT_EQ(f.check("Red"), -EINVAL); +} + +TEST_F(FaultInjectorString, AssignErrorMessage) +{ + FaultInjector<std::string_view> f; + ASSERT_EQ(f.check("Red"), 0); + f.inject("Red", InjectError{-EINVAL, dpp()}); + EXPECT_EQ(f.check("Red"), -EINVAL); +} + +// test enum class as a Key type +using FaultInjectorEnum = Fixture; + +enum class Color { Red, Green, Blue }; + +static std::ostream& operator<<(std::ostream& out, const Color& c) { + switch (c) { + case Color::Red: return out << "Red"; + case Color::Green: return out << "Green"; + case Color::Blue: return out << "Blue"; + } + return out; +} + +TEST_F(FaultInjectorEnum, Default) +{ + constexpr FaultInjector<Color> f; + EXPECT_EQ(f.check(Color::Red), 0); + EXPECT_EQ(f.check(Color::Green), 0); + EXPECT_EQ(f.check(Color::Blue), 0); +} + +TEST_F(FaultInjectorEnum, InjectError) +{ + FaultInjector f{Color::Red, InjectError{-EIO}}; + EXPECT_EQ(f.check(Color::Red), -EIO); + EXPECT_EQ(f.check(Color::Green), 0); + EXPECT_EQ(f.check(Color::Blue), 0); +} + +TEST_F(FaultInjectorEnum, InjectErrorMessage) +{ + FaultInjector f{Color::Red, InjectError{-EIO, dpp()}}; + EXPECT_EQ(f.check(Color::Red), -EIO); + EXPECT_EQ(f.check(Color::Green), 0); + EXPECT_EQ(f.check(Color::Blue), 0); +} + +TEST_F(FaultInjectorEnum, AssignError) +{ + FaultInjector<Color> f; + ASSERT_EQ(f.check(Color::Red), 0); + f.inject(Color::Red, InjectError{-EINVAL}); + EXPECT_EQ(f.check(Color::Red), -EINVAL); +} + +TEST_F(FaultInjectorEnum, AssignErrorMessage) +{ + FaultInjector<Color> f; + ASSERT_EQ(f.check(Color::Red), 0); + f.inject(Color::Red, InjectError{-EINVAL, dpp()}); + EXPECT_EQ(f.check(Color::Red), -EINVAL); +} + +// test custom move-only Key type +using FaultInjectorMoveOnly = Fixture; + +struct MoveOnlyKey { + MoveOnlyKey() = default; + MoveOnlyKey(const MoveOnlyKey&) = delete; + MoveOnlyKey& operator=(const MoveOnlyKey&) = delete; + MoveOnlyKey(MoveOnlyKey&&) = default; + MoveOnlyKey& operator=(MoveOnlyKey&&) = default; + ~MoveOnlyKey() = default; +}; + +static bool operator==(const MoveOnlyKey&, const MoveOnlyKey&) { + return true; // all keys are equal +} +static std::ostream& operator<<(std::ostream& out, const MoveOnlyKey&) { + return out; +} + +TEST_F(FaultInjectorMoveOnly, Default) +{ + constexpr FaultInjector<MoveOnlyKey> f; + EXPECT_EQ(f.check(MoveOnlyKey{}), 0); +} + +TEST_F(FaultInjectorMoveOnly, InjectError) +{ + FaultInjector f{MoveOnlyKey{}, InjectError{-EIO}}; + EXPECT_EQ(f.check(MoveOnlyKey{}), -EIO); +} + +TEST_F(FaultInjectorMoveOnly, InjectErrorMessage) +{ + FaultInjector f{MoveOnlyKey{}, InjectError{-EIO, dpp()}}; + EXPECT_EQ(f.check(MoveOnlyKey{}), -EIO); +} + +TEST_F(FaultInjectorMoveOnly, AssignError) +{ + FaultInjector<MoveOnlyKey> f; + ASSERT_EQ(f.check({}), 0); + f.inject({}, InjectError{-EINVAL}); + EXPECT_EQ(f.check({}), -EINVAL); +} + +TEST_F(FaultInjectorMoveOnly, AssignErrorMessage) +{ + FaultInjector<MoveOnlyKey> f; + ASSERT_EQ(f.check({}), 0); + f.inject({}, InjectError{-EINVAL, dpp()}); + EXPECT_EQ(f.check({}), -EINVAL); +} diff --git a/src/test/common/test_global_doublefree.cc b/src/test/common/test_global_doublefree.cc new file mode 100644 index 000000000..ef8fefb6b --- /dev/null +++ b/src/test/common/test_global_doublefree.cc @@ -0,0 +1,30 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2016 Red Hat, Inc. + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + + +/* + * This test is linked against librados and libcephfs to try and detect issues + * with global, static, non-POD variables as seen in the following trackers. + * http://tracker.ceph.com/issues/16504 + * http://tracker.ceph.com/issues/16686 + * In those trackers such variables caused segfaults with glibc reporting + * "double free or corruption". + * + * Don't be fooled by its emptiness. It does serve a purpose :) + */ + +int main(int, char**) +{ + return 0; +} diff --git a/src/test/common/test_hobject.cc b/src/test/common/test_hobject.cc new file mode 100644 index 000000000..0bb4aef9e --- /dev/null +++ b/src/test/common/test_hobject.cc @@ -0,0 +1,11 @@ +#include "common/hobject.h" +#include "gtest/gtest.h" + +TEST(HObject, cmp) +{ + hobject_t c{object_t{"fooc"}, "food", CEPH_NOSNAP, 42, 0, "nspace"}; + hobject_t d{object_t{"food"}, "", CEPH_NOSNAP, 42, 0, "nspace"}; + hobject_t e{object_t{"fooe"}, "food", CEPH_NOSNAP, 42, 0, "nspace"}; + ASSERT_EQ(-1, cmp(c, d)); + ASSERT_EQ(-1, cmp(d, e)); +} diff --git a/src/test/common/test_hostname.cc b/src/test/common/test_hostname.cc new file mode 100644 index 000000000..b0e631d20 --- /dev/null +++ b/src/test/common/test_hostname.cc @@ -0,0 +1,71 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net> + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "gtest/gtest.h" +#include "common/hostname.h" +#include "common/SubProcess.h" +#include "stdio.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "unistd.h" + +#include <array> +#include <iostream> +#include <stdexcept> +#include <stdio.h> +#include <string> +#include <memory> + +std::string exec(const char* cmd) { + std::array<char, 128> buffer; + std::string result; + std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose); + if (!pipe) throw std::runtime_error("popen() failed!"); + while (!feof(pipe.get())) { + if (fgets(buffer.data(), 128, pipe.get()) != NULL) + result += buffer.data(); + } + // remove \n + return result.substr(0, result.size()-1);; +} + +TEST(Hostname, full) { + std::string hn = ceph_get_hostname(); + if (const char *nn = getenv("NODE_NAME")) { + // we are in a container + std::cout << "we are in a container on " << nn << ", reporting " << hn + << std::endl; + ASSERT_EQ(hn, nn); + } else { + ASSERT_EQ(hn, exec("hostname")) ; + } +} + +TEST(Hostname, short) { + std::string shn = ceph_get_short_hostname(); + if (const char *nn = getenv("NODE_NAME")) { + // we are in a container + std::cout << "we are in a container on " << nn << ", reporting short " << shn + << ", skipping test because env var may or may not be short form" + << std::endl; + } else { + #ifdef _WIN32 + ASSERT_EQ(shn, exec("hostname")); + #else + ASSERT_EQ(shn, exec("hostname -s")); + #endif + } +} diff --git a/src/test/common/test_interval_map.cc b/src/test/common/test_interval_map.cc new file mode 100644 index 000000000..99f676f28 --- /dev/null +++ b/src/test/common/test_interval_map.cc @@ -0,0 +1,337 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2016 Red Hat + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <gtest/gtest.h> +#include <boost/random/mersenne_twister.hpp> +#include <boost/random/uniform_int_distribution.hpp> +#include <boost/mpl/apply.hpp> +#include "include/buffer.h" +#include "common/interval_map.h" + +using namespace std; + +template<typename T> +class IntervalMapTest : public ::testing::Test { +public: + using TestType = T; +}; + +template <typename _key> +struct bufferlist_test_type { + using key = _key; + using value = bufferlist; + + struct make_splitter { + template <typename merge_t> + struct apply { + bufferlist split( + key offset, + key len, + bufferlist &bu) const { + bufferlist bl; + bl.substr_of(bu, offset, len); + return bl; + } + bool can_merge(const bufferlist &left, const bufferlist &right) const { + return merge_t::value; + } + bufferlist merge(bufferlist &&left, bufferlist &&right) const { + bufferlist bl; + left.claim_append(right); + return std::move(left); + } + uint64_t length(const bufferlist &r) const { + return r.length(); + } + }; + }; + + struct generate_random { + bufferlist operator()(key len) { + bufferlist bl; + boost::random::mt19937 rng; + boost::random::uniform_int_distribution<> chr(0,255); + for (key i = 0; i < len; ++i) { + bl.append((char)chr(rng)); + } + return bl; + } + }; +}; + +using IntervalMapTypes = ::testing::Types< bufferlist_test_type<uint64_t> >; + +TYPED_TEST_SUITE(IntervalMapTest, IntervalMapTypes); + +#define USING(_can_merge) \ + using TT = typename TestFixture::TestType; \ + using key = typename TT::key; (void)key(0); \ + using val = typename TT::value; (void)val(0); \ + using splitter = typename boost::mpl::apply< \ + typename TT::make_splitter, \ + _can_merge>; \ + using imap = interval_map<key, val, splitter>; (void)imap(); \ + typename TT::generate_random gen; \ + val v(gen(5)); \ + splitter split; (void)split.split(0, 0, v); + +#define USING_NO_MERGE USING(std::false_type) +#define USING_WITH_MERGE USING(std::true_type) + +TYPED_TEST(IntervalMapTest, empty) { + USING_NO_MERGE; + imap m; + ASSERT_TRUE(m.empty()); +} + +TYPED_TEST(IntervalMapTest, insert) { + USING_NO_MERGE; + imap m; + vector<val> vals{gen(5), gen(5), gen(5)}; + m.insert(0, 5, vals[0]); + m.insert(10, 5, vals[2]); + m.insert(5, 5, vals[1]); + ASSERT_EQ(m.ext_count(), 3u); + + unsigned i = 0; + for (auto &&ext: m) { + ASSERT_EQ(ext.get_len(), 5u); + ASSERT_EQ(ext.get_off(), 5u * i); + ASSERT_EQ(ext.get_val(), vals[i]); + ++i; + } + ASSERT_EQ(i, m.ext_count()); +} + +TYPED_TEST(IntervalMapTest, insert_begin_overlap) { + USING_NO_MERGE; + imap m; + vector<val> vals{gen(5), gen(5), gen(5)}; + m.insert(5, 5, vals[1]); + m.insert(10, 5, vals[2]); + m.insert(1, 5, vals[0]); + + auto iter = m.begin(); + ASSERT_EQ(iter.get_off(), 1u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[0]); + ++iter; + + ASSERT_EQ(iter.get_off(), 6u); + ASSERT_EQ(iter.get_len(), 4u); + ASSERT_EQ(iter.get_val(), split.split(1, 4, vals[1])); + ++iter; + + ASSERT_EQ(iter.get_off(), 10u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[2]); + ++iter; + + ASSERT_EQ(iter, m.end()); +} + +TYPED_TEST(IntervalMapTest, insert_end_overlap) { + USING_NO_MERGE; + imap m; + vector<val> vals{gen(5), gen(5), gen(5)}; + m.insert(0, 5, vals[0]); + m.insert(5, 5, vals[1]); + m.insert(8, 5, vals[2]); + + auto iter = m.begin(); + ASSERT_EQ(iter.get_off(), 0u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[0]); + ++iter; + + ASSERT_EQ(iter.get_off(), 5u); + ASSERT_EQ(iter.get_len(), 3u); + ASSERT_EQ(iter.get_val(), split.split(0, 3, vals[1])); + ++iter; + + ASSERT_EQ(iter.get_off(), 8u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[2]); + ++iter; + + ASSERT_EQ(iter, m.end()); +} + +TYPED_TEST(IntervalMapTest, insert_middle_overlap) { + USING_NO_MERGE; + imap m; + vector<val> vals{gen(5), gen(7), gen(5)}; + m.insert(0, 5, vals[0]); + m.insert(10, 5, vals[2]); + m.insert(4, 7, vals[1]); + + auto iter = m.begin(); + ASSERT_EQ(iter.get_off(), 0u); + ASSERT_EQ(iter.get_len(), 4u); + ASSERT_EQ(iter.get_val(), split.split(0, 4, vals[0])); + ++iter; + + ASSERT_EQ(iter.get_off(), 4u); + ASSERT_EQ(iter.get_len(), 7u); + ASSERT_EQ(iter.get_val(), vals[1]); + ++iter; + + ASSERT_EQ(iter.get_off(), 11u); + ASSERT_EQ(iter.get_len(), 4u); + ASSERT_EQ(iter.get_val(), split.split(1, 4, vals[2])); + ++iter; + + ASSERT_EQ(iter, m.end()); +} + +TYPED_TEST(IntervalMapTest, insert_single_exact_overlap) { + USING_NO_MERGE; + imap m; + vector<val> vals{gen(5), gen(5), gen(5)}; + m.insert(0, 5, gen(5)); + m.insert(5, 5, vals[1]); + m.insert(10, 5, vals[2]); + m.insert(0, 5, vals[0]); + + auto iter = m.begin(); + ASSERT_EQ(iter.get_off(), 0u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[0]); + ++iter; + + ASSERT_EQ(iter.get_off(), 5u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[1]); + ++iter; + + ASSERT_EQ(iter.get_off(), 10u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[2]); + ++iter; + + ASSERT_EQ(iter, m.end()); +} + +TYPED_TEST(IntervalMapTest, insert_single_exact_overlap_end) { + USING_NO_MERGE; + imap m; + vector<val> vals{gen(5), gen(5), gen(5)}; + m.insert(0, 5, vals[0]); + m.insert(5, 5, vals[1]); + m.insert(10, 5, gen(5)); + m.insert(10, 5, vals[2]); + + auto iter = m.begin(); + ASSERT_EQ(iter.get_off(), 0u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[0]); + ++iter; + + ASSERT_EQ(iter.get_off(), 5u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[1]); + ++iter; + + ASSERT_EQ(iter.get_off(), 10u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[2]); + ++iter; + + ASSERT_EQ(iter, m.end()); +} + +TYPED_TEST(IntervalMapTest, erase) { + USING_NO_MERGE; + imap m; + vector<val> vals{gen(5), gen(5), gen(5)}; + m.insert(0, 5, vals[0]); + m.insert(5, 5, vals[1]); + m.insert(10, 5, vals[2]); + + m.erase(3, 5); + + auto iter = m.begin(); + ASSERT_EQ(iter.get_off(), 0u); + ASSERT_EQ(iter.get_len(), 3u); + ASSERT_EQ(iter.get_val(), split.split(0, 3, vals[0])); + ++iter; + + ASSERT_EQ(iter.get_off(), 8u); + ASSERT_EQ(iter.get_len(), 2u); + ASSERT_EQ(iter.get_val(), split.split(3, 2, vals[1])); + ++iter; + + ASSERT_EQ(iter.get_off(), 10u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[2]); + ++iter; + + ASSERT_EQ(iter, m.end()); +} + +TYPED_TEST(IntervalMapTest, erase_exact) { + USING_NO_MERGE; + imap m; + vector<val> vals{gen(5), gen(5), gen(5)}; + m.insert(0, 5, vals[0]); + m.insert(5, 5, vals[1]); + m.insert(10, 5, vals[2]); + + m.erase(5, 5); + + auto iter = m.begin(); + ASSERT_EQ(iter.get_off(), 0u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[0]); + ++iter; + + ASSERT_EQ(iter.get_off(), 10u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[2]); + ++iter; + + ASSERT_EQ(iter, m.end()); +} + +TYPED_TEST(IntervalMapTest, get_containing_range) { + USING_NO_MERGE; + imap m; + vector<val> vals{gen(5), gen(5), gen(5), gen(5)}; + m.insert(0, 5, vals[0]); + m.insert(10, 5, vals[1]); + m.insert(20, 5, vals[2]); + m.insert(30, 5, vals[3]); + + auto rng = m.get_containing_range(5, 21); + auto iter = rng.first; + + ASSERT_EQ(iter.get_off(), 10u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[1]); + ++iter; + + ASSERT_EQ(iter.get_off(), 20u); + ASSERT_EQ(iter.get_len(), 5u); + ASSERT_EQ(iter.get_val(), vals[2]); + ++iter; + + ASSERT_EQ(iter, rng.second); +} + +TYPED_TEST(IntervalMapTest, merge) { + USING_WITH_MERGE; + imap m; + m.insert(10, 4, gen(4)); + m.insert(11, 1, gen(1)); +} diff --git a/src/test/common/test_interval_set.cc b/src/test/common/test_interval_set.cc new file mode 100644 index 000000000..7eb1dcbe2 --- /dev/null +++ b/src/test/common/test_interval_set.cc @@ -0,0 +1,600 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2015 Mirantis, Inc. + * + * Author: Igor Fedotov <ifedotov@mirantis.com> + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <gtest/gtest.h> +#include <boost/container/flat_map.hpp> +#include "include/interval_set.h" +#include "include/btree_map.h" + +using namespace ceph; + +typedef uint64_t IntervalValueType; + +template<typename T> // tuple<type to test on, test array size> +class IntervalSetTest : public ::testing::Test { + + public: + typedef T ISet; +}; + +typedef ::testing::Types< + interval_set<IntervalValueType>, + interval_set<IntervalValueType, btree::btree_map>, + interval_set<IntervalValueType, boost::container::flat_map> + > IntervalSetTypes; + +TYPED_TEST_SUITE(IntervalSetTest, IntervalSetTypes); + +TYPED_TEST(IntervalSetTest, compare) { + typedef typename TestFixture::ISet ISet; + ISet iset1, iset2; + ASSERT_TRUE(iset1 == iset1); + ASSERT_TRUE(iset1 == iset2); + + iset1.insert(1); + ASSERT_FALSE(iset1 == iset2); + + iset2.insert(1); + ASSERT_TRUE(iset1 == iset2); + + iset1.insert(2, 3); + iset2.insert(2, 4); + ASSERT_FALSE(iset1 == iset2); + + iset2.erase(2, 4); + iset2.erase(1); + iset2.insert(2, 3); + iset2.insert(1); + ASSERT_TRUE(iset1 == iset2); + + iset1.insert(100, 10); + iset2.insert(100, 5); + ASSERT_FALSE(iset1 == iset2); + iset2.insert(105, 5); + ASSERT_TRUE(iset1 == iset2); + + iset1.insert(200, 10); + iset2.insert(205, 5); + ASSERT_FALSE(iset1 == iset2); + iset2.insert(200, 1); + iset2.insert(202, 3); + ASSERT_FALSE(iset1 == iset2); + iset2.insert(201, 1); + ASSERT_TRUE(iset1 == iset2); + + iset1.clear(); + ASSERT_FALSE(iset1 == iset2); + iset2.clear(); + ASSERT_TRUE(iset1 == iset2); +} + +TYPED_TEST(IntervalSetTest, contains) { + typedef typename TestFixture::ISet ISet; + ISet iset1; + ASSERT_FALSE(iset1.contains( 1 )); + ASSERT_FALSE(iset1.contains( 0, 1 )); + + iset1.insert(1); + ASSERT_TRUE(iset1.contains( 1 )); + ASSERT_FALSE(iset1.contains( 0 )); + ASSERT_FALSE(iset1.contains( 2 )); + ASSERT_FALSE(iset1.contains( 0, 1 )); + ASSERT_FALSE(iset1.contains( 0, 2 )); + ASSERT_TRUE(iset1.contains( 1, 1 )); + ASSERT_FALSE(iset1.contains( 1, 2 )); + + iset1.insert(2, 3); + ASSERT_TRUE(iset1.contains( 1 )); + ASSERT_FALSE(iset1.contains( 0 )); + ASSERT_TRUE(iset1.contains( 2 )); + ASSERT_FALSE(iset1.contains( 0, 1 )); + ASSERT_FALSE(iset1.contains( 0, 2 )); + ASSERT_TRUE(iset1.contains( 1, 1 )); + ASSERT_TRUE(iset1.contains( 1, 2 )); + ASSERT_TRUE(iset1.contains( 1, 3 )); + ASSERT_TRUE(iset1.contains( 1, 4 )); + ASSERT_FALSE(iset1.contains( 1, 5 )); + ASSERT_TRUE(iset1.contains( 2, 1 )); + ASSERT_TRUE(iset1.contains( 2, 2 )); + ASSERT_TRUE(iset1.contains( 2, 3 )); + ASSERT_FALSE(iset1.contains( 2, 4 )); + ASSERT_TRUE(iset1.contains( 3, 2 )); + ASSERT_TRUE(iset1.contains( 4, 1 )); + ASSERT_FALSE(iset1.contains( 4, 2 )); + + iset1.insert(10, 10); + ASSERT_TRUE(iset1.contains( 1, 4 )); + ASSERT_FALSE(iset1.contains( 1, 5 )); + ASSERT_TRUE(iset1.contains( 2, 2 )); + ASSERT_FALSE(iset1.contains( 2, 4 )); + + ASSERT_FALSE(iset1.contains( 1, 10 )); + ASSERT_FALSE(iset1.contains( 9, 1 )); + ASSERT_FALSE(iset1.contains( 9 )); + ASSERT_FALSE(iset1.contains( 9, 11 )); + ASSERT_TRUE(iset1.contains( 10, 1 )); + ASSERT_TRUE(iset1.contains( 11, 9 )); + ASSERT_TRUE(iset1.contains( 11, 2 )); + ASSERT_TRUE(iset1.contains( 18, 2 )); + ASSERT_TRUE(iset1.contains( 18, 2 )); + ASSERT_TRUE(iset1.contains( 10 )); + ASSERT_TRUE(iset1.contains( 19 )); + ASSERT_FALSE(iset1.contains( 20 )); + ASSERT_FALSE(iset1.contains( 21 )); + + ASSERT_FALSE(iset1.contains( 11, 11 )); + ASSERT_FALSE(iset1.contains( 18, 9 )); + + iset1.clear(); + ASSERT_FALSE(iset1.contains( 1 )); + ASSERT_FALSE(iset1.contains( 0 )); + ASSERT_FALSE(iset1.contains( 2 )); + ASSERT_FALSE(iset1.contains( 0, 1 )); + ASSERT_FALSE(iset1.contains( 0, 2 )); + ASSERT_FALSE(iset1.contains( 1, 1 )); + ASSERT_FALSE(iset1.contains( 10, 2 )); +} + +TYPED_TEST(IntervalSetTest, intersects) { + typedef typename TestFixture::ISet ISet; + ISet iset1; + ASSERT_FALSE(iset1.intersects( 1, 1 )); + ASSERT_FALSE(iset1.intersects( 0, 1 )); + ASSERT_FALSE(iset1.intersects( 0, 10 )); + + iset1.insert(1); + ASSERT_TRUE(iset1.intersects( 1, 1 )); + ASSERT_FALSE(iset1.intersects( 0, 1 )); + ASSERT_FALSE(iset1.intersects( 2, 1 )); + ASSERT_TRUE(iset1.intersects( 0, 2 )); + ASSERT_TRUE(iset1.intersects( 0, 20 )); + ASSERT_TRUE(iset1.intersects( 1, 2 )); + ASSERT_TRUE(iset1.intersects( 1, 20 )); + + iset1.insert(2, 3); + ASSERT_FALSE(iset1.intersects( 0, 1 )); + ASSERT_TRUE(iset1.intersects( 0, 2 )); + ASSERT_TRUE(iset1.intersects( 0, 200 )); + ASSERT_TRUE(iset1.intersects( 1, 1 )); + ASSERT_TRUE(iset1.intersects( 1, 4 )); + ASSERT_TRUE(iset1.intersects( 1, 5 )); + ASSERT_TRUE(iset1.intersects( 2, 1 )); + ASSERT_TRUE(iset1.intersects( 2, 2 )); + ASSERT_TRUE(iset1.intersects( 2, 3 )); + ASSERT_TRUE(iset1.intersects( 2, 4 )); + ASSERT_TRUE(iset1.intersects( 3, 2 )); + ASSERT_TRUE(iset1.intersects( 4, 1 )); + ASSERT_TRUE(iset1.intersects( 4, 2 )); + ASSERT_FALSE(iset1.intersects( 5, 2 )); + + iset1.insert(10, 10); + ASSERT_TRUE(iset1.intersects( 1, 4 )); + ASSERT_TRUE(iset1.intersects( 1, 5 )); + ASSERT_TRUE(iset1.intersects( 1, 10 )); + ASSERT_TRUE(iset1.intersects( 2, 2 )); + ASSERT_TRUE(iset1.intersects( 2, 4 )); + ASSERT_FALSE(iset1.intersects( 5, 1 )); + ASSERT_FALSE(iset1.intersects( 5, 2 )); + ASSERT_FALSE(iset1.intersects( 5, 5 )); + ASSERT_TRUE(iset1.intersects( 5, 12 )); + ASSERT_TRUE(iset1.intersects( 5, 20 )); + + ASSERT_FALSE(iset1.intersects( 9, 1 )); + ASSERT_TRUE(iset1.intersects( 9, 2 )); + + ASSERT_TRUE(iset1.intersects( 9, 11 )); + ASSERT_TRUE(iset1.intersects( 10, 1 )); + ASSERT_TRUE(iset1.intersects( 11, 9 )); + ASSERT_TRUE(iset1.intersects( 11, 2 )); + ASSERT_TRUE(iset1.intersects( 11, 11 )); + ASSERT_TRUE(iset1.intersects( 18, 2 )); + ASSERT_TRUE(iset1.intersects( 18, 9 )); + ASSERT_FALSE(iset1.intersects( 20, 1 )); + ASSERT_FALSE(iset1.intersects( 21, 12 )); + + iset1.clear(); + ASSERT_FALSE(iset1.intersects( 0, 1 )); + ASSERT_FALSE(iset1.intersects( 0, 2 )); + ASSERT_FALSE(iset1.intersects( 1, 1 )); + ASSERT_FALSE(iset1.intersects( 5, 2 )); + ASSERT_FALSE(iset1.intersects( 10, 2 )); +} + +TYPED_TEST(IntervalSetTest, insert_erase) { + typedef typename TestFixture::ISet ISet; + ISet iset1, iset2; + IntervalValueType start, len; + + iset1.insert(3, 5, &start, &len); + ASSERT_EQ(3, start); + ASSERT_EQ(5, len); + ASSERT_EQ(1, iset1.num_intervals()); + ASSERT_EQ(5, iset1.size()); + + //adding standalone interval + iset1.insert(15, 10, &start, &len); + ASSERT_EQ(15, start); + ASSERT_EQ(10, len); + ASSERT_EQ(2, iset1.num_intervals()); + ASSERT_EQ(15, iset1.size()); + + //adding leftmost standalone interval + iset1.insert(1, 1, &start, &len); + ASSERT_EQ(1, start); + ASSERT_EQ(1, len); + ASSERT_EQ(3, iset1.num_intervals()); + ASSERT_EQ(16, iset1.size()); + + //adding leftmost adjucent interval + iset1.insert(0, 1, &start, &len); + ASSERT_EQ(0, start); + ASSERT_EQ(2, len); + ASSERT_EQ(3, iset1.num_intervals()); + ASSERT_EQ(17, iset1.size()); + + //adding interim interval that merges leftmost and subseqent intervals + iset1.insert(2, 1, &start, &len); + ASSERT_EQ(0, start); + ASSERT_EQ(8, len); + ASSERT_EQ(2, iset1.num_intervals()); + ASSERT_EQ(18, iset1.size()); + + //adding rigtmost standalone interval + iset1.insert(30, 5, &start, &len); + ASSERT_EQ(30, start); + ASSERT_EQ(5, len); + ASSERT_EQ(3, iset1.num_intervals()); + ASSERT_EQ(23, iset1.size()); + + //adding rigtmost adjusent interval + iset1.insert(35, 10, &start, &len); + ASSERT_EQ(30, start); + ASSERT_EQ(15, len ); + ASSERT_EQ(3, iset1.num_intervals()); + ASSERT_EQ(33, iset1.size()); + + //adding interim interval that merges with the interval preceeding the rightmost + iset1.insert(25, 1, &start, &len); + ASSERT_EQ(15, start); + ASSERT_EQ(11, len); + ASSERT_EQ(3, iset1.num_intervals()); + ASSERT_EQ(34, iset1.size()); + + //adding interim interval that merges with the rightmost and preceeding intervals + iset1.insert(26, 4, &start, &len); + ASSERT_EQ(15, start); + ASSERT_EQ(30, len); + ASSERT_EQ(2, iset1.num_intervals()); + ASSERT_EQ(38, iset1.size()); + + //and finally build single interval filling the gap at 8-15 using different interval set + iset2.insert( 8, 1 ); + iset2.insert( 14, 1 ); + iset2.insert( 9, 4 ); + iset1.insert( iset2 ); + iset1.insert(13, 1, &start, &len); + ASSERT_EQ(0, start); + ASSERT_EQ(45, len); + ASSERT_EQ(1, iset1.num_intervals()); + ASSERT_EQ(45, iset1.size()); + + //now reverses the process using subtract & erase + iset1.subtract( iset2 ); + iset1.erase(13, 1); + ASSERT_EQ( 2, iset1.num_intervals() ); + ASSERT_EQ(38, iset1.size()); + ASSERT_TRUE( iset1.contains( 7, 1 )); + ASSERT_FALSE( iset1.contains( 8, 7 )); + ASSERT_TRUE( iset1.contains( 15, 1 )); + ASSERT_TRUE( iset1.contains( 26, 4 )); + + iset1.erase(26, 4); + ASSERT_EQ(3, iset1.num_intervals()); + ASSERT_EQ(34, iset1.size()); + ASSERT_TRUE( iset1.contains( 7, 1 )); + ASSERT_FALSE( iset1.intersects( 8, 7 )); + ASSERT_TRUE( iset1.contains( 15, 1 )); + ASSERT_TRUE( iset1.contains( 25, 1 )); + ASSERT_FALSE( iset1.contains( 26, 4 )); + ASSERT_TRUE( iset1.contains( 30, 1 )); + + iset1.erase(25, 1); + ASSERT_EQ(3, iset1.num_intervals()); + ASSERT_EQ(33, iset1.size()); + ASSERT_TRUE( iset1.contains( 24, 1 )); + ASSERT_FALSE( iset1.contains( 25, 1 )); + ASSERT_FALSE( iset1.intersects( 26, 4 )); + ASSERT_TRUE( iset1.contains( 30, 1 )); + ASSERT_TRUE( iset1.contains( 35, 10 )); + + iset1.erase(35, 10); + ASSERT_EQ(3, iset1.num_intervals()); + ASSERT_EQ(23, iset1.size()); + ASSERT_TRUE( iset1.contains( 30, 5 )); + ASSERT_TRUE( iset1.contains( 34, 1 )); + ASSERT_FALSE( iset1.contains( 35, 10 )); + ASSERT_FALSE(iset1.contains( 45, 1 )); + + iset1.erase(30, 5); + ASSERT_EQ(2, iset1.num_intervals()); + ASSERT_EQ(18, iset1.size()); + ASSERT_TRUE( iset1.contains( 2, 1 )); + ASSERT_TRUE( iset1.contains( 24, 1 )); + ASSERT_FALSE( iset1.contains( 25, 1 )); + ASSERT_FALSE( iset1.contains( 29, 1 )); + ASSERT_FALSE( iset1.contains( 30, 5 )); + ASSERT_FALSE( iset1.contains( 35, 1 )); + + iset1.erase(2, 1); + ASSERT_EQ(3, iset1.num_intervals()); + ASSERT_EQ( iset1.size(), 17 ); + ASSERT_TRUE( iset1.contains( 0, 1 )); + ASSERT_TRUE( iset1.contains( 1, 1 )); + ASSERT_FALSE( iset1.contains( 2, 1 )); + ASSERT_TRUE( iset1.contains( 3, 1 )); + ASSERT_TRUE( iset1.contains( 15, 1 )); + ASSERT_FALSE( iset1.contains( 25, 1 )); + + iset1.erase( 0, 1); + ASSERT_EQ(3, iset1.num_intervals()); + ASSERT_EQ(16, iset1.size()); + ASSERT_FALSE( iset1.contains( 0, 1 )); + ASSERT_TRUE( iset1.contains( 1, 1 )); + ASSERT_FALSE( iset1.contains( 2, 1 )); + ASSERT_TRUE( iset1.contains( 3, 1 )); + ASSERT_TRUE( iset1.contains( 15, 1 )); + + iset1.erase(1, 1); + ASSERT_EQ(2, iset1.num_intervals()); + ASSERT_EQ(15, iset1.size()); + ASSERT_FALSE( iset1.contains( 1, 1 )); + ASSERT_TRUE( iset1.contains( 15, 10 )); + ASSERT_TRUE( iset1.contains( 3, 5 )); + + iset1.erase(15, 10); + ASSERT_EQ(1, iset1.num_intervals()); + ASSERT_EQ(5, iset1.size()); + ASSERT_FALSE( iset1.contains( 1, 1 )); + ASSERT_FALSE( iset1.contains( 15, 10 )); + ASSERT_FALSE( iset1.contains( 25, 1 )); + ASSERT_TRUE( iset1.contains( 3, 5 )); + + iset1.erase( 3, 1); + ASSERT_EQ(1, iset1.num_intervals()); + ASSERT_EQ(4, iset1.size()); + ASSERT_FALSE( iset1.contains( 1, 1 )); + ASSERT_FALSE( iset1.contains( 15, 10 )); + ASSERT_FALSE( iset1.contains( 25, 1 )); + ASSERT_TRUE( iset1.contains( 4, 4 )); + ASSERT_FALSE( iset1.contains( 3, 5 )); + + iset1.erase( 4, 4); + ASSERT_EQ(0, iset1.num_intervals()); + ASSERT_EQ(0, iset1.size()); + ASSERT_FALSE( iset1.contains( 1, 1 )); + ASSERT_FALSE( iset1.contains( 15, 10 )); + ASSERT_FALSE( iset1.contains( 25, 1 )); + ASSERT_FALSE( iset1.contains( 3, 4 )); + ASSERT_FALSE( iset1.contains( 3, 5 )); + ASSERT_FALSE( iset1.contains( 4, 4 )); + + +} + +TYPED_TEST(IntervalSetTest, intersect_of) { + typedef typename TestFixture::ISet ISet; + ISet iset1, iset2, iset3; + + iset1.intersection_of( iset2, iset3 ); + ASSERT_TRUE( iset1.num_intervals() == 0); + ASSERT_TRUE( iset1.size() == 0); + + iset2.insert( 0, 1 ); + iset2.insert( 5, 10 ); + iset2.insert( 30, 10 ); + + iset3.insert( 0, 2 ); + iset3.insert( 15, 1 ); + iset3.insert( 20, 5 ); + iset3.insert( 29, 3 ); + iset3.insert( 35, 3 ); + iset3.insert( 39, 3 ); + + iset1.intersection_of( iset2, iset3 ); + ASSERT_TRUE( iset1.num_intervals() == 4); + ASSERT_TRUE( iset1.size() == 7); + + ASSERT_TRUE( iset1.contains( 0, 1 )); + ASSERT_FALSE( iset1.contains( 0, 2 )); + + ASSERT_FALSE( iset1.contains( 5, 11 )); + ASSERT_FALSE( iset1.contains( 4, 1 )); + ASSERT_FALSE( iset1.contains( 16, 1 )); + + ASSERT_FALSE( iset1.contains( 20, 5 )); + + ASSERT_FALSE( iset1.contains( 29, 1 )); + ASSERT_FALSE( iset1.contains( 30, 10 )); + + ASSERT_TRUE( iset1.contains( 30, 2 )); + ASSERT_TRUE( iset1.contains( 35, 3 )); + ASSERT_FALSE( iset1.contains( 35, 4 )); + + ASSERT_TRUE( iset1.contains( 39, 1 )); + ASSERT_FALSE( iset1.contains( 38, 2 )); + ASSERT_FALSE( iset1.contains( 39, 2 )); + + iset3=iset1; + iset1.intersection_of(iset2); + ASSERT_TRUE( iset1 == iset3); + + iset2.clear(); + iset2.insert(0,1); + iset1.intersection_of(iset2); + ASSERT_TRUE( iset1.num_intervals() == 1); + ASSERT_TRUE( iset1.size() == 1); + + iset1 = iset3; + iset2.clear(); + iset1.intersection_of(iset2); + ASSERT_TRUE( iset1.num_intervals() == 0); + ASSERT_TRUE( iset1.size() == 0); + +} + +TYPED_TEST(IntervalSetTest, union_of) { + typedef typename TestFixture::ISet ISet; + ISet iset1, iset2, iset3; + + iset1.union_of( iset2, iset3 ); + ASSERT_TRUE( iset1.num_intervals() == 0); + ASSERT_TRUE( iset1.size() == 0); + + iset2.insert( 0, 1 ); + iset2.insert( 5, 10 ); + iset2.insert( 30, 10 ); + + iset3.insert( 0, 2 ); + iset3.insert( 15, 1 ); + iset3.insert( 20, 5 ); + iset3.insert( 29, 3 ); + iset3.insert( 39, 3 ); + + iset1.union_of( iset2, iset3 ); + ASSERT_TRUE( iset1.num_intervals() == 4); + ASSERT_EQ( iset1.size(), 31); + ASSERT_TRUE( iset1.contains( 0, 2 )); + ASSERT_FALSE( iset1.contains( 0, 3 )); + + ASSERT_TRUE( iset1.contains( 5, 11 )); + ASSERT_FALSE( iset1.contains( 4, 1 )); + ASSERT_FALSE( iset1.contains( 16, 1 )); + + ASSERT_TRUE( iset1.contains( 20, 5 )); + + ASSERT_TRUE( iset1.contains( 30, 10 )); + ASSERT_TRUE( iset1.contains( 29, 13 )); + ASSERT_FALSE( iset1.contains( 29, 14 )); + ASSERT_FALSE( iset1.contains( 42, 1 )); + + iset2.clear(); + iset1.union_of(iset2); + ASSERT_TRUE( iset1.num_intervals() == 4); + ASSERT_EQ( iset1.size(), 31); + + iset3.clear(); + iset3.insert( 29, 3 ); + iset3.insert( 39, 2 ); + iset1.union_of(iset3); + + ASSERT_TRUE( iset1.num_intervals() == 4); + ASSERT_EQ( iset1.size(), 31); //actually we added nothing + ASSERT_TRUE( iset1.contains( 29, 13 )); + ASSERT_FALSE( iset1.contains( 29, 14 )); + ASSERT_FALSE( iset1.contains( 42, 1 )); + +} + +TYPED_TEST(IntervalSetTest, subset_of) { + typedef typename TestFixture::ISet ISet; + ISet iset1, iset2; + + ASSERT_TRUE(iset1.subset_of(iset2)); + + iset1.insert(5,10); + ASSERT_FALSE(iset1.subset_of(iset2)); + + iset2.insert(6,8); + ASSERT_FALSE(iset1.subset_of(iset2)); + + iset2.insert(5,1); + ASSERT_FALSE(iset1.subset_of(iset2)); + + iset2.insert(14,10); + ASSERT_TRUE(iset1.subset_of(iset2)); + + iset1.insert( 20, 4); + ASSERT_TRUE(iset1.subset_of(iset2)); + + iset1.insert( 24, 1); + ASSERT_FALSE(iset1.subset_of(iset2)); + + iset2.insert( 24, 1); + ASSERT_TRUE(iset1.subset_of(iset2)); + + iset1.insert( 30, 5); + ASSERT_FALSE(iset1.subset_of(iset2)); + + iset2.insert( 30, 5); + ASSERT_TRUE(iset1.subset_of(iset2)); + + iset2.erase( 30, 1); + ASSERT_FALSE(iset1.subset_of(iset2)); + + iset1.erase( 30, 1); + ASSERT_TRUE(iset1.subset_of(iset2)); + + iset2.erase( 34, 1); + ASSERT_FALSE(iset1.subset_of(iset2)); + + iset1.erase( 34, 1); + ASSERT_TRUE(iset1.subset_of(iset2)); + + iset1.insert( 40, 5); + ASSERT_FALSE(iset1.subset_of(iset2)); + + iset2.insert( 39, 7); + ASSERT_TRUE(iset1.subset_of(iset2)); + + iset1.insert( 50, 5); + iset2.insert( 55, 2); + ASSERT_FALSE(iset1.subset_of(iset2)); +} + +TYPED_TEST(IntervalSetTest, span_of) { + typedef typename TestFixture::ISet ISet; + ISet iset1, iset2; + + iset2.insert(5,5); + iset2.insert(20,5); + + iset1.span_of( iset2, 8, 5 ); + ASSERT_EQ( iset1.num_intervals(), 2); + ASSERT_EQ( iset1.size(), 5); + ASSERT_TRUE( iset1.contains( 8, 2 )); + ASSERT_TRUE( iset1.contains( 20, 3 )); + + iset1.span_of( iset2, 3, 5 ); + ASSERT_EQ( iset1.num_intervals(), 1); + ASSERT_EQ( iset1.size(), 5); + ASSERT_TRUE( iset1.contains( 5, 5 )); + + iset1.span_of( iset2, 10, 7 ); + ASSERT_EQ( iset1.num_intervals(), 1); + ASSERT_EQ( iset1.size(), 5); + ASSERT_TRUE( iset1.contains( 20, 5 )); + ASSERT_FALSE( iset1.contains( 20, 6 )); + + iset1.span_of( iset2, 5, 10); + ASSERT_EQ( iset1.num_intervals(), 2); + ASSERT_EQ( iset1.size(), 10); + ASSERT_TRUE( iset1.contains( 5, 5 )); + ASSERT_TRUE( iset1.contains( 20, 5 )); + + iset1.span_of( iset2, 100, 5 ); + ASSERT_EQ( iset1.num_intervals(), 0); + ASSERT_EQ( iset1.size(), 0); +} diff --git a/src/test/common/test_intrusive_lru.cc b/src/test/common/test_intrusive_lru.cc new file mode 100644 index 000000000..0654bd97d --- /dev/null +++ b/src/test/common/test_intrusive_lru.cc @@ -0,0 +1,208 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include <stdio.h> +#include "gtest/gtest.h" +#include "common/intrusive_lru.h" + +template <typename TestLRUItem> +struct item_to_unsigned { + using type = unsigned; + const type &operator()(const TestLRUItem &item) { + return item.key; + } +}; + +struct TestLRUItem : public ceph::common::intrusive_lru_base< + ceph::common::intrusive_lru_config< + unsigned, TestLRUItem, item_to_unsigned<TestLRUItem>>> { + unsigned key = 0; + int value = 0; + + TestLRUItem(unsigned key) : key(key) {} +}; + +class LRUTest : public TestLRUItem::lru_t { +public: + auto add(unsigned int key, int value) { + auto [ref, key_existed] = get_or_create(key); + if (!key_existed) { + ref->value = value; + } + return std::pair(ref, key_existed); + } +}; + +TEST(LRU, add_immediate_evict) { + LRUTest cache; + unsigned int key = 1; + int value1 = 2; + int value2 = 3; + { + auto [ref, existed] = cache.add(key, value1); + ASSERT_TRUE(ref); + ASSERT_EQ(value1, ref->value); + ASSERT_FALSE(existed); + } + { + auto [ref2, existed] = cache.add(key, value2); + ASSERT_EQ(value2, ref2->value); + ASSERT_FALSE(existed); + } +} + +TEST(LRU, lookup_lru_size) { + LRUTest cache; + int key = 1; + int value = 1; + cache.set_target_size(1); + { + auto [ref, existed] = cache.add(key, value); + ASSERT_TRUE(ref); + ASSERT_FALSE(existed); + } + { + auto [ref, existed] = cache.add(key, value); + ASSERT_TRUE(ref); + ASSERT_TRUE(existed); + } + cache.set_target_size(0); + auto [ref2, existed2] = cache.add(key, value); + ASSERT_TRUE(ref2); + ASSERT_FALSE(existed2); + { + auto [ref, existed] = cache.add(key, value); + ASSERT_TRUE(ref); + ASSERT_TRUE(existed); + } +} + +TEST(LRU, eviction) { + const unsigned SIZE = 3; + LRUTest cache; + cache.set_target_size(SIZE); + + for (unsigned i = 0; i < SIZE; ++i) { + auto [ref, existed] = cache.add(i, i); + ASSERT_TRUE(ref && !existed); + } + + { + auto [ref, existed] = cache.add(0, 0); + ASSERT_TRUE(ref && existed); + } + + for (unsigned i = SIZE; i < (2*SIZE) - 1; ++i) { + auto [ref, existed] = cache.add(i, i); + ASSERT_TRUE(ref && !existed); + } + + { + auto [ref, existed] = cache.add(0, 0); + ASSERT_TRUE(ref && existed); + } + + for (unsigned i = 1; i < SIZE; ++i) { + auto [ref, existed] = cache.add(i, i); + ASSERT_TRUE(ref && !existed); + } +} + +TEST(LRU, eviction_live_ref) { + const unsigned SIZE = 3; + LRUTest cache; + cache.set_target_size(SIZE); + + auto [live_ref, existed2] = cache.add(1, 1); + ASSERT_TRUE(live_ref && !existed2); + + for (unsigned i = 0; i < SIZE; ++i) { + auto [ref, existed] = cache.add(i, i); + ASSERT_TRUE(ref); + if (i == 1) { + ASSERT_TRUE(existed); + } else { + ASSERT_FALSE(existed); + } + } + + { + auto [ref, existed] = cache.add(0, 0); + ASSERT_TRUE(ref && existed); + } + + for (unsigned i = SIZE; i < (2*SIZE) - 1; ++i) { + auto [ref, existed] = cache.add(i, i); + ASSERT_TRUE(ref && !existed); + } + + for (unsigned i = 0; i < SIZE; ++i) { + auto [ref, existed] = cache.add(i, i); + ASSERT_TRUE(ref); + if (i == 1) { + ASSERT_TRUE(existed); + } else { + ASSERT_FALSE(existed); + } + } +} + +TEST(LRU, clear_range) { + LRUTest cache; + const unsigned SIZE = 10; + cache.set_target_size(SIZE); + { + auto [ref, existed] = cache.add(1, 4); + ASSERT_FALSE(existed); + } + { + auto [ref, existed] = cache.add(2, 4); + ASSERT_FALSE(existed); + } + { + auto [ref, existed] = cache.add(3, 4); + ASSERT_FALSE(existed); + } + // Unlike above, the reference is not being destroyed + auto [live_ref1, existed1] = cache.add(4, 4); + ASSERT_FALSE(existed1); + + auto [live_ref2, existed2] = cache.add(5, 4); + ASSERT_FALSE(existed2); + + cache.clear_range(0,4); + + // Should not exists (Unreferenced): + { + auto [ref, existed] = cache.add(1, 4); + ASSERT_FALSE(existed); + } + { + auto [ref, existed] = cache.add(2, 4); + ASSERT_FALSE(existed); + } + { + auto [ref, existed] = cache.add(3, 4); + ASSERT_FALSE(existed); + } + // Should exist (Still being referenced): + { + auto [ref, existed] = cache.add(4, 4); + ASSERT_TRUE(existed); + } + // Should exists (Still being referenced and wasn't removed) + { + auto [ref, existed] = cache.add(5, 4); + ASSERT_TRUE(existed); + } + // Test out of bound deletion: + { + cache.clear_range(3,8); + auto [ref, existed] = cache.add(4, 4); + ASSERT_TRUE(existed); + } + { + auto [ref, existed] = cache.add(3, 4); + ASSERT_FALSE(existed); + } +} diff --git a/src/test/common/test_iso_8601.cc b/src/test/common/test_iso_8601.cc new file mode 100644 index 000000000..e012c99c5 --- /dev/null +++ b/src/test/common/test_iso_8601.cc @@ -0,0 +1,60 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2017 Red Hat <contact@redhat.com> + * + * LGPL-2.1 (see COPYING-LGPL2.1) or later + */ + +#include <chrono> + +#include <gtest/gtest.h> + +#include "common/ceph_time.h" +#include "common/iso_8601.h" + +using std::chrono::minutes; +using std::chrono::seconds; +using std::chrono::time_point_cast; + +using ceph::from_iso_8601; +using ceph::iso_8601_format; +using ceph::real_clock; +using ceph::real_time; +using ceph::to_iso_8601; + +TEST(iso_8601, epoch) { + const auto epoch = real_clock::from_time_t(0); + + ASSERT_EQ("1970", to_iso_8601(epoch, iso_8601_format::Y)); + ASSERT_EQ("1970-01", to_iso_8601(epoch, iso_8601_format::YM)); + ASSERT_EQ("1970-01-01", to_iso_8601(epoch, iso_8601_format::YMD)); + ASSERT_EQ("1970-01-01T00Z", to_iso_8601(epoch, iso_8601_format::YMDh)); + ASSERT_EQ("1970-01-01T00:00Z", to_iso_8601(epoch, iso_8601_format::YMDhm)); + ASSERT_EQ("1970-01-01T00:00:00Z", + to_iso_8601(epoch, iso_8601_format::YMDhms)); + ASSERT_EQ("1970-01-01T00:00:00.000000000Z", + to_iso_8601(epoch, iso_8601_format::YMDhmsn)); + + ASSERT_EQ(epoch, *from_iso_8601("1970")); + ASSERT_EQ(epoch, *from_iso_8601("1970-01")); + ASSERT_EQ(epoch, *from_iso_8601("1970-01-01")); + ASSERT_EQ(epoch, *from_iso_8601("1970-01-01T00:00Z")); + ASSERT_EQ(epoch, *from_iso_8601("1970-01-01T00:00:00Z")); + ASSERT_EQ(epoch, *from_iso_8601("1970-01-01T00:00:00.000000000Z")); +} + +TEST(iso_8601, now) { + const auto now = real_clock::now(); + + ASSERT_EQ(real_time(time_point_cast<minutes>(now)), + *from_iso_8601(to_iso_8601(now, iso_8601_format::YMDhm))); + ASSERT_EQ(real_time(time_point_cast<seconds>(now)), + *from_iso_8601( + to_iso_8601(now, iso_8601_format::YMDhms))); + ASSERT_EQ(now, + *from_iso_8601( + to_iso_8601(now, iso_8601_format::YMDhmsn))); +} diff --git a/src/test/common/test_journald_logger.cc b/src/test/common/test_journald_logger.cc new file mode 100644 index 000000000..cf8df6dbc --- /dev/null +++ b/src/test/common/test_journald_logger.cc @@ -0,0 +1,41 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include <cerrno> +#include <gtest/gtest.h> +#include <sys/stat.h> + +#include "common/Journald.h" +#include "log/Entry.h" +#include "log/SubsystemMap.h" + +using namespace ceph::logging; + +class JournaldLoggerTest : public ::testing::Test { + protected: + SubsystemMap subs; + JournaldLogger journald = {&subs}; + MutableEntry entry = {0, 0}; + + void SetUp() override { + struct stat buffer; + if (stat("/run/systemd/journal/socket", &buffer) < 0) { + if (errno == ENOENT) { + GTEST_SKIP() << "No journald socket present."; + } + FAIL() << "Unexpected stat error: " << strerror(errno); + } + } +}; + +TEST_F(JournaldLoggerTest, Log) +{ + entry.get_ostream() << "This is a testing regular log message."; + EXPECT_EQ(journald.log_entry(entry), 0); +} + +TEST_F(JournaldLoggerTest, VeryLongLog) +{ + entry.get_ostream() << std::string(16 * 1024 * 1024, 'a'); + EXPECT_EQ(journald.log_entry(entry), 0); +} diff --git a/src/test/common/test_json_formattable.cc b/src/test/common/test_json_formattable.cc new file mode 100644 index 000000000..62448e808 --- /dev/null +++ b/src/test/common/test_json_formattable.cc @@ -0,0 +1,453 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2018 Red Hat Inc. + * + * 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 <errno.h> +#include <gtest/gtest.h> + +#include "common/ceph_json.h" + +#include <sstream> + +using namespace std; + + +static void get_jf(const string& s, JSONFormattable *f) +{ + JSONParser p; + bool result = p.parse(s.c_str(), s.size()); + if (!result) { + cout << "Failed to parse: '" << s << "'" << std::endl; + } + ASSERT_EQ(true, result); + try { + decode_json_obj(*f, &p); + } catch (JSONDecoder::err& e) { + ASSERT_TRUE(0 == "Failed to decode JSON object"); + } +} + +TEST(formatable, str) { + JSONFormattable f; + get_jf("{ \"foo\": \"bar\" }", &f); + ASSERT_EQ((string)f["foo"], "bar"); + ASSERT_EQ((string)f["fooz"], ""); + ASSERT_EQ((string)f["fooz"]("lala"), "lala"); +} + +TEST(formatable, str2) { + JSONFormattable f; + get_jf("{ \"foo\": \"bar\" }", &f); + ASSERT_EQ((string)f["foo"], "bar"); + ASSERT_EQ((string)f["fooz"], ""); + ASSERT_EQ((string)f["fooz"]("lala"), "lala"); + + JSONFormattable f2; + get_jf("{ \"foo\": \"bar\", \"fooz\": \"zzz\" }", &f2); + ASSERT_EQ((string)f2["foo"], "bar"); + ASSERT_NE((string)f2["fooz"], ""); + ASSERT_EQ((string)f2["fooz"], "zzz"); + ASSERT_EQ((string)f2["fooz"]("lala"), "zzz"); + +} + +TEST(formatable, str3) { + JSONFormattable f; + get_jf("{ \"foo\": \"1234bar56\" }", &f); + ASSERT_EQ((string)f["foo"], "1234bar56"); +} + +TEST(formatable, int) { + JSONFormattable f; + get_jf("{ \"foo\": 1 }", &f); + ASSERT_EQ((int)f["foo"], 1); + ASSERT_EQ((int)f["fooz"], 0); + ASSERT_EQ((int)f["fooz"](3), 3); + + JSONFormattable f2; + get_jf("{ \"foo\": \"bar\", \"fooz\": \"123\" }", &f2); + ASSERT_EQ((string)f2["foo"], "bar"); + ASSERT_NE((int)f2["fooz"], 0); + ASSERT_EQ((int)f2["fooz"], 123); + ASSERT_EQ((int)f2["fooz"](111), 123); +} + +TEST(formatable, bool) { + JSONFormattable f; + get_jf("{ \"foo\": \"true\" }", &f); + ASSERT_EQ((bool)f["foo"], true); + ASSERT_EQ((bool)f["fooz"], false); + ASSERT_EQ((bool)f["fooz"](true), true); + + JSONFormattable f2; + get_jf("{ \"foo\": \"false\" }", &f); + ASSERT_EQ((bool)f["foo"], false); +} + +TEST(formatable, nested) { + JSONFormattable f; + get_jf("{ \"obj\": { \"foo\": 1, \"inobj\": { \"foo\": 2 } } }", &f); + ASSERT_EQ((int)f["foo"], 0); + ASSERT_EQ((int)f["obj"]["foo"], 1); + ASSERT_EQ((int)f["obj"]["inobj"]["foo"], 2); +} + +TEST(formatable, array) { + JSONFormattable f; + get_jf("{ \"arr\": [ { \"foo\": 1, \"inobj\": { \"foo\": 2 } }," + "{ \"foo\": 2 } ] }", &f); + + int i = 1; + for (auto a : f.array()) { + ASSERT_EQ((int)a["foo"], i); + ++i; + } + + JSONFormattable f2; + get_jf("{ \"arr\": [ 0, 1, 2, 3, 4 ]}", &f2); + + i = 0; + for (auto a : f2.array()) { + ASSERT_EQ((int)a, i); + ++i; + } +} + +TEST(formatable, bin_encode) { + JSONFormattable f, f2; + get_jf("{ \"arr\": [ { \"foo\": 1, \"bar\": \"aaa\", \"inobj\": { \"foo\": 2 } }," + "{ \"foo\": 2, \"inobj\": { \"foo\": 3 } } ] }", &f); + + int i = 1; + for (auto a : f.array()) { + ASSERT_EQ((int)a["foo"], i); + ASSERT_EQ((int)a["foo"]["inobj"], i + 1); + ASSERT_EQ((string)a["bar"], "aaa"); + ++i; + } + + bufferlist bl; + ::encode(f, bl); + auto iter = bl.cbegin(); + try { + ::decode(f2, iter); + } catch (buffer::error& err) { + ASSERT_TRUE(0 == "Failed to decode object"); + } + + i = 1; + for (auto a : f2.array()) { + ASSERT_EQ((int)a["foo"], i); + ASSERT_EQ((int)a["foo"]["inobj"], i + 1); + ASSERT_EQ((string)a["bar"], "aaa"); + ++i; + } + +} + +TEST(formatable, json_encode) { + JSONFormattable f, f2; + get_jf("{ \"arr\": [ { \"foo\": 1, \"bar\": \"aaa\", \"inobj\": { \"foo\": 2 } }," + "{ \"foo\": 2, \"inobj\": { \"foo\": 3 } } ] }", &f); + + JSONFormatter formatter; + formatter.open_object_section("bla"); + ::encode_json("f", f, &formatter); + formatter.close_section(); + + stringstream ss; + formatter.flush(ss); + + get_jf(ss.str(), &f2); + + int i = 1; + for (auto a : f2.array()) { + ASSERT_EQ((int)a["foo"], i); + ASSERT_EQ((int)a["foo"]["inobj"], i + 1); + ASSERT_EQ((string)a["bar"], "aaa"); + ++i; + } + +} + +TEST(formatable, set) { + JSONFormattable f, f2; + + f.set("", "{ \"abc\": \"xyz\"}"); + ASSERT_EQ((string)f["abc"], "xyz"); + + f.set("aaa", "111"); + ASSERT_EQ((string)f["abc"], "xyz"); + ASSERT_EQ((int)f["aaa"], 111); + + f.set("obj", "{ \"a\": \"10\", \"b\": \"20\"}"); + ASSERT_EQ((int)f["obj"]["a"], 10); + ASSERT_EQ((int)f["obj"]["b"], 20); + + f.set("obj.c", "30"); + + ASSERT_EQ((int)f["obj"]["c"], 30); +} + +TEST(formatable, set2) { + JSONFormattable f; + f.set("foo", "1234bar56"); + ASSERT_EQ((string)f["foo"], "1234bar56"); +} + +TEST(formatable, erase) { + JSONFormattable f, f2; + + f.set("", "{ \"abc\": \"xyz\"}"); + ASSERT_EQ((string)f["abc"], "xyz"); + + f.set("aaa", "111"); + ASSERT_EQ((string)f["abc"], "xyz"); + ASSERT_EQ((int)f["aaa"], 111); + f.erase("aaa"); + ASSERT_EQ((int)f["aaa"], 0); + + f.set("obj", "{ \"a\": \"10\", \"b\": \"20\"}"); + ASSERT_EQ((int)f["obj"]["a"], 10); + ASSERT_EQ((int)f["obj"]["b"], 20); + f.erase("obj.a"); + ASSERT_EQ((int)f["obj"]["a"], 0); + ASSERT_EQ((int)f["obj"]["b"], 20); +} + +template <class T> +static void dumpt(const T& t, const char *n) +{ + JSONFormatter formatter; + formatter.open_object_section("bla"); + ::encode_json(n, t, &formatter); + formatter.close_section(); + formatter.flush(cout); +} + +static void dumpf(const JSONFormattable& f) { + dumpt(f, "f"); +} + +TEST(formatable, set_array) { + JSONFormattable f, f2; + + f.set("asd[0]", "\"xyz\""); + ASSERT_EQ(1u, f["asd"].array().size()); + ASSERT_EQ((string)f["asd"][0], "xyz"); + + f.set("bbb[0][0]", "10"); + f.set("bbb[0][1]", "20"); + ASSERT_EQ(1u, f["bbb"].array().size()); + ASSERT_EQ(2u, f["bbb"][0].array().size()); + ASSERT_EQ("10", (string)f["bbb"][0][0]); + ASSERT_EQ(20, (int)f["bbb"][0][1]); + f.set("bbb[0][1]", "25"); + ASSERT_EQ(2u, f["bbb"][0].array().size()); + ASSERT_EQ(25, (int)f["bbb"][0][1]); + + f.set("bbb[0][]", "26"); /* append operation */ + ASSERT_EQ(26, (int)f["bbb"][0][2]); + f.set("bbb[0][-1]", "27"); /* replace last */ + ASSERT_EQ(27, (int)f["bbb"][0][2]); + ASSERT_EQ(3u, f["bbb"][0].array().size()); + + f.set("foo.asd[0][0]", "{ \"field\": \"xyz\"}"); + ASSERT_EQ((string)f["foo"]["asd"][0][0]["field"], "xyz"); + + ASSERT_EQ(f.set("foo[0]", "\"zzz\""), -EINVAL); /* can't assign array to an obj entity */ + + f2.set("[0]", "{ \"field\": \"xyz\"}"); + ASSERT_EQ((string)f2[0]["field"], "xyz"); +} + +TEST(formatable, erase_array) { + JSONFormattable f; + + f.set("asd[0]", "\"xyz\""); + ASSERT_EQ(1u, f["asd"].array().size()); + ASSERT_EQ("xyz", (string)f["asd"][0]); + ASSERT_TRUE(f["asd"].exists(0)); + f.erase("asd[0]"); + ASSERT_FALSE(f["asd"].exists(0)); + f.set("asd[0]", "\"xyz\""); + ASSERT_TRUE(f["asd"].exists(0)); + f["asd"].erase("[0]"); + ASSERT_FALSE(f["asd"].exists(0)); + f.set("asd[0]", "\"xyz\""); + ASSERT_TRUE(f["asd"].exists(0)); + f.erase("asd"); + ASSERT_FALSE(f["asd"].exists(0)); + ASSERT_FALSE(f.exists("asd")); + + f.set("bbb[]", "10"); + f.set("bbb[]", "20"); + f.set("bbb[]", "30"); + ASSERT_EQ((int)f["bbb"][0], 10); + ASSERT_EQ((int)f["bbb"][1], 20); + ASSERT_EQ((int)f["bbb"][2], 30); + f.erase("bbb[-2]"); + ASSERT_FALSE(f.exists("bbb[2]")); + + ASSERT_EQ((int)f["bbb"][0], 10); + ASSERT_EQ((int)f["bbb"][1], 30); + + if (0) { /* for debugging when needed */ + dumpf(f); + } +} + +void formatter_convert(JSONFormatter& formatter, JSONFormattable *dest) +{ + stringstream ss; + formatter.flush(ss); + get_jf(ss.str(), dest); +} + +TEST(formatable, encode_simple) { + JSONFormattable f; + + encode_json("foo", "bar", &f); + + ASSERT_EQ((string)f["foo"], "bar"); + + + JSONFormatter formatter; + { + Formatter::ObjectSection s(formatter, "os"); + encode_json("f", f, &formatter); + } + + JSONFormattable jf2; + formatter_convert(formatter, &jf2); + + ASSERT_EQ((string)jf2["f"]["foo"], "bar"); +} + + +struct struct1 { + long i; + string s; + bool b; + + struct1() { + void *p = (void *)this; + i = (long)p; + char buf[32]; + snprintf(buf, sizeof(buf), "%p", p); + s = buf; + b = (bool)(i % 2); + } + + void dump(Formatter *f) const { + encode_json("i", i, f); + encode_json("s", s, f); + encode_json("b", b, f); + } + + void decode_json(JSONObj *obj) { + JSONDecoder::decode_json("i", i, obj); + JSONDecoder::decode_json("s", s, obj); + JSONDecoder::decode_json("b", b, obj); + } + + bool compare(const JSONFormattable& jf) const { + bool ret = (s == (string)jf["s"] && + i == (long)jf["i"] && + b == (bool)jf["b"]); + + if (!ret) { + cout << "failed comparison: s=" << s << " jf[s]=" << (string)jf["s"] << + " i=" << i << " jf[i]=" << (long)jf["i"] << " b=" << b << " jf[b]=" << (bool)jf["b"] << std::endl; + dumpf(jf); + } + + return ret; + } +}; + + +struct struct2 { + struct1 s1; + vector<struct1> v; + + struct2() { + void *p = (void *)this; + long i = (long)p; + v.resize((i >> 16) % 16 + 1); + } + + void dump(Formatter *f) const { + encode_json("s1", s1, f); + encode_json("v", v, f); + } + + void decode_json(JSONObj *obj) { + JSONDecoder::decode_json("s1", s1, obj); + JSONDecoder::decode_json("v", v, obj); + } + + bool compare(const JSONFormattable& jf) const { + if (!s1.compare(jf["s1"])) { + cout << "s1.compare(jf[s1] failed" << std::endl; + return false; + } + + if (v.size() != jf["v"].array().size()) { + cout << "v.size()=" << v.size() << " jf[v].array().size()=" << jf["v"].array().size() << std::endl; + return false; + } + + auto viter = v.begin(); + auto jiter = jf["v"].array().begin(); + + for (; viter != v.end(); ++viter, ++jiter) { + if (!viter->compare(*jiter)) { + return false; + } + } + return true; + } +}; + + +TEST(formatable, encode_struct) { + JSONFormattable f; + + struct2 s2; + + { + Formatter::ObjectSection s(f, "section"); + encode_json("foo", "bar", &f); + encode_json("s2", s2, &f); + } + + dumpt(s2, "s2"); + cout << std::endl; + cout << std::endl; + + ASSERT_EQ((string)f["foo"], "bar"); + ASSERT_TRUE(s2.compare(f["s2"])); + + + JSONFormatter formatter; + encode_json("f", f, &formatter); + + JSONFormattable jf2; + + formatter_convert(formatter, &jf2); + + ASSERT_EQ((string)jf2["foo"], "bar"); + ASSERT_TRUE(s2.compare(jf2["s2"])); +} + diff --git a/src/test/common/test_json_formatter.cc b/src/test/common/test_json_formatter.cc new file mode 100644 index 000000000..8a0f547a9 --- /dev/null +++ b/src/test/common/test_json_formatter.cc @@ -0,0 +1,81 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2018 Red Hat Inc. + * + * 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 <errno.h> +#include <gtest/gtest.h> + +#include "common/ceph_json.h" +#include "common/Clock.h" + +#include <sstream> + +using namespace std; + + +TEST(formatter, bug_37706) { + vector<std::string> pgs; + + string outstring = +"{\"pg_ready\":true, \"pg_stats\":[ { \"pgid\":\"1.0\", \"version\":\"16'56\",\"reported_seq\":\"62\",\"reported_epoch\":\"20\",\"state\":\"active+clean+inconsistent\",\"last_fresh\":\"2018-12-18 15:21:22.173804\",\"last_change\":\"2018-12-18 15:21:22.173804\",\"last_active\":\"2018-12-18 15:21:22.173804\",\"last_peered\":\"2018-12-18 15:21:22.173804\",\"last_clean\":\"2018-12-18 15:21:22.173804\",\"last_became_active\":\"2018-12-18 15:21:21.347685\",\"last_became_peered\":\"2018-12-18 15:21:21.347685\",\"last_unstale\":\"2018-12-18 15:21:22.173804\",\"last_undegraded\":\"2018-12-18 15:21:22.173804\",\"last_fullsized\":\"2018-12-18 15:21:22.173804\",\"mapping_epoch\":19,\"log_start\":\"0'0\",\"ondisk_log_start\":\"0'0\",\"created\":7,\"last_epoch_clean\":20,\"parent\":\"0.0\",\"parent_split_bits\":0,\"last_scrub\":\"16'56\",\"last_scrub_stamp\":\"2018-12-18 15:21:22.173684\",\"last_deep_scrub\":\"0'0\",\"last_deep_scrub_stamp\":\"2018-12-18 15:21:06.514438\",\"last_clean_scrub_stamp\":\"2018-12-18 15:21:06.514438\",\"log_size\":56,\"ondisk_log_size\":56,\"stats_invalid\":false,\"dirty_stats_invalid\":false,\"omap_stats_invalid\":false,\"hitset_stats_invalid\":false,\"hitset_bytes_stats_invalid\":false,\"pin_stats_invalid\":false,\"manifest_stats_invalid\":false,\"snaptrimq_len\":0,\"stat_sum\":{\"num_bytes\":24448,\"num_objects\":36,\"num_object_clones\":20,\"num_object_copies\":36,\"num_objects_missing_on_primary\":0,\"num_objects_missing\":0,\"num_objects_degraded\":0,\"num_objects_misplaced\":0,\"num_objects_unfound\":0,\"num_objects_dirty\":36,\"num_whiteouts\":3,\"num_read\":0,\"num_read_kb\":0,\"num_write\":36,\"num_write_kb\":50,\"num_scrub_errors\":20,\"num_shallow_scrub_errors\":20,\"num_deep_scrub_errors\":0,\"num_objects_recovered\":0,\"num_bytes_recovered\":0,\"num_keys_recovered\":0,\"num_objects_omap\":0,\"num_objects_hit_set_archive\":0,\"num_bytes_hit_set_archive\":0,\"num_flush\":0,\"num_flush_kb\":0,\"num_evict\":0,\"num_evict_kb\":0,\"num_promote\":0,\"num_flush_mode_high\":0,\"num_flush_mode_low\":0,\"num_evict_mode_some\":0,\"num_evict_mode_full\":0,\"num_objects_pinned\":0,\"num_legacy_snapsets\":0,\"num_large_omap_objects\":0,\"num_objects_manifest\":0},\"up\":[0],\"acting\":[0],\"blocked_by\":[],\"up_primary\":0,\"acting_primary\":0,\"purged_snaps\":[] }]}"; + + + JSONParser parser; + ASSERT_TRUE(parser.parse(outstring.c_str(), outstring.size())); + + vector<string> v; + + ASSERT_TRUE (!parser.is_array()); + + JSONObj *pgstat_obj = parser.find_obj("pg_stats"); + ASSERT_TRUE (!!pgstat_obj); + auto s = pgstat_obj->get_data(); + + ASSERT_TRUE(!s.empty()); + JSONParser pg_stats; + ASSERT_TRUE(pg_stats.parse(s.c_str(), s.length())); + v = pg_stats.get_array_elements(); + + for (auto i : v) { + JSONParser pg_json; + ASSERT_TRUE(pg_json.parse(i.c_str(), i.length())); + string pgid; + JSONDecoder::decode_json("pgid", pgid, &pg_json); + pgs.emplace_back(std::move(pgid)); + } + + ASSERT_EQ(pgs.back(), "1.0"); +} + +TEST(formatter, utime) +{ + JSONFormatter formatter; + + utime_t input = ceph_clock_now(); + input.gmtime_nsec(formatter.dump_stream("timestamp")); + + bufferlist bl; + formatter.flush(bl); + + JSONParser parser; + EXPECT_TRUE(parser.parse(bl.c_str(), bl.length())); + + cout << input << " -> '" << std::string(bl.c_str(), bl.length()) + << std::endl; + + utime_t output; + decode_json_obj(output, &parser); + cout << " -> " << output << std::endl; + EXPECT_EQ(input.sec(), output.sec()); + EXPECT_EQ(input.nsec(), output.nsec()); +} diff --git a/src/test/common/test_lockdep.cc b/src/test/common/test_lockdep.cc new file mode 100644 index 000000000..7a5ab6f1e --- /dev/null +++ b/src/test/common/test_lockdep.cc @@ -0,0 +1,74 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "gtest/gtest.h" + +#include "common/ceph_argparse.h" +#include "common/ceph_context.h" +#include "common/ceph_mutex.h" +#include "common/common_init.h" +#include "common/lockdep.h" +#include "include/util.h" +#include "include/coredumpctl.h" +#include "log/Log.h" + +class lockdep : public ::testing::Test +{ +protected: + void SetUp() override { +#ifndef CEPH_DEBUG_MUTEX + GTEST_SKIP() << "WARNING: CEPH_DEBUG_MUTEX is not defined, lockdep will not work"; +#endif + CephInitParameters params(CEPH_ENTITY_TYPE_CLIENT); + cct = common_preinit(params, CODE_ENVIRONMENT_UTILITY, + CINIT_FLAG_NO_DEFAULT_CONFIG_FILE); + cct->_conf->cluster = "ceph"; + cct->_conf.set_val("lockdep", "true"); + cct->_conf.apply_changes(nullptr); + ASSERT_TRUE(g_lockdep); + } + void TearDown() final + { + if (cct) { + cct->put(); + cct = nullptr; + } + } +protected: + CephContext *cct = nullptr; +}; + +TEST_F(lockdep, abba) +{ + ceph::mutex a(ceph::make_mutex("a")), b(ceph::make_mutex("b")); + a.lock(); + ASSERT_TRUE(ceph_mutex_is_locked(a)); + ASSERT_TRUE(ceph_mutex_is_locked_by_me(a)); + b.lock(); + ASSERT_TRUE(ceph_mutex_is_locked(b)); + ASSERT_TRUE(ceph_mutex_is_locked_by_me(b)); + a.unlock(); + b.unlock(); + + b.lock(); + PrCtl unset_dumpable; + EXPECT_DEATH(a.lock(), ""); + b.unlock(); +} + +TEST_F(lockdep, recursive) +{ + ceph::mutex a(ceph::make_mutex("a")); + a.lock(); + PrCtl unset_dumpable; + EXPECT_DEATH(a.lock(), ""); + a.unlock(); + + ceph::recursive_mutex b(ceph::make_recursive_mutex("b")); + b.lock(); + ASSERT_TRUE(ceph_mutex_is_locked(b)); + ASSERT_TRUE(ceph_mutex_is_locked_by_me(b)); + b.lock(); + b.unlock(); + b.unlock(); +} diff --git a/src/test/common/test_lru.cc b/src/test/common/test_lru.cc new file mode 100644 index 000000000..29f32aac3 --- /dev/null +++ b/src/test/common/test_lru.cc @@ -0,0 +1,158 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2014 Cloudwatt <libre.licensing@cloudwatt.com> + * + * Author: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@cloudwatt.com> + * + * 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 <errno.h> +#include <gtest/gtest.h> + +#include "include/lru.h" + + +class Item : public LRUObject { +public: + int id; + Item() : id(0) {} + explicit Item(int i) : id(i) {} + void set(int i) {id = i;} +}; + + +TEST(lru, InsertTop) { + LRU lru; + static const int n = 100; + Item items[n]; + + lru.lru_set_midpoint(.5); // 50% of elements. + for (int i=0; i<n; i++) { + items[i].set(i); + lru.lru_insert_top(&items[i]); + } + ASSERT_EQ(50U, lru.lru_get_top()); + ASSERT_EQ(50U, lru.lru_get_bot()); + ASSERT_EQ(100U, lru.lru_get_size()); + + ASSERT_EQ(0, (static_cast<Item*>(lru.lru_expire()))->id); +} + +TEST(lru, InsertMid) { + LRU lru; + static const int n = 102; + Item items[n]; + + lru.lru_set_midpoint(.7); // 70% of elements. + for (int i=0; i<n; i++) { + items[i].set(i); + lru.lru_insert_mid(&items[i]); + } + ASSERT_EQ(71U, lru.lru_get_top()); + ASSERT_EQ(31U, lru.lru_get_bot()); + ASSERT_EQ(102U, lru.lru_get_size()); + + ASSERT_EQ(0, (static_cast<Item*>(lru.lru_expire()))->id); +} + +TEST(lru, InsertBot) { + LRU lru; + static const int n = 100; + Item items[n]; + + lru.lru_set_midpoint(.7); // 70% of elements. + for (int i=0; i<n; i++) { + items[i].set(i); + lru.lru_insert_bot(&items[i]); + } + ASSERT_EQ(70U, lru.lru_get_top()); + ASSERT_EQ(30U, lru.lru_get_bot()); + ASSERT_EQ(100U, lru.lru_get_size()); + + ASSERT_EQ(99, (static_cast<Item*>(lru.lru_expire()))->id); +} + +TEST(lru, Adjust) { + LRU lru; + static const int n = 100; + Item items[n]; + + lru.lru_set_midpoint(.6); // 60% of elements. + for (int i=0; i<n; i++) { + items[i].set(i); + lru.lru_insert_top(&items[i]); + if (i % 5 == 0) + items[i].lru_pin(); + } + ASSERT_EQ(48U, lru.lru_get_top()); /* 60% of unpinned */ + ASSERT_EQ(52U, lru.lru_get_bot()); + ASSERT_EQ(100U, lru.lru_get_size()); + + ASSERT_EQ(1, (static_cast<Item*>(lru.lru_expire()))->id); + ASSERT_EQ(1U, lru.lru_get_pintail()); + ASSERT_EQ(47U, lru.lru_get_top()); /* 60% of unpinned */ + ASSERT_EQ(51U, lru.lru_get_bot()); + ASSERT_EQ(99U, lru.lru_get_size()); + ASSERT_EQ(2, (static_cast<Item*>(lru.lru_expire()))->id); + ASSERT_EQ(1U, lru.lru_get_pintail()); + ASSERT_EQ(46U, lru.lru_get_top()); /* 60% of unpinned */ + ASSERT_EQ(51U, lru.lru_get_bot()); + ASSERT_EQ(98U, lru.lru_get_size()); + ASSERT_EQ(3, (static_cast<Item*>(lru.lru_expire()))->id); + ASSERT_EQ(4, (static_cast<Item*>(lru.lru_expire()))->id); + ASSERT_EQ(6, (static_cast<Item*>(lru.lru_expire()))->id); + ASSERT_EQ(2U, lru.lru_get_pintail()); + ASSERT_EQ(45U, lru.lru_get_top()); /* 60% of unpinned */ + ASSERT_EQ(48U, lru.lru_get_bot()); + ASSERT_EQ(95U, lru.lru_get_size()); +} + +TEST(lru, Pinning) { + LRU lru; + + Item ob0(0), ob1(1); + + // test before ob1 are in a LRU + ob1.lru_pin(); + ASSERT_FALSE(ob1.lru_is_expireable()); + + ob1.lru_unpin(); + ASSERT_TRUE(ob1.lru_is_expireable()); + + // test when ob1 are in a LRU + lru.lru_insert_top(&ob0); + lru.lru_insert_top(&ob1); + + ob1.lru_pin(); + ob1.lru_pin(); // Verify that, one incr. + ASSERT_EQ(1U, lru.lru_get_num_pinned()); + ASSERT_FALSE(ob1.lru_is_expireable()); + + ob1.lru_unpin(); + ob1.lru_unpin(); // Verify that, one decr. + ASSERT_EQ(0U, lru.lru_get_num_pinned()); + ASSERT_TRUE(ob1.lru_is_expireable()); + + ASSERT_EQ(0, (static_cast<Item*>(lru.lru_expire()))->id); + ob0.lru_pin(); + ASSERT_EQ(1, (static_cast<Item*>(lru.lru_expire()))->id); +} + + +/* + * Local Variables: + * compile-command: "cd ../.. ; make -j4 && + * make unittest_lru && + * valgrind --tool=memcheck --leak-check=full \ + * ./unittest_lru + * " + * End: + */ diff --git a/src/test/common/test_lruset.cc b/src/test/common/test_lruset.cc new file mode 100644 index 000000000..64e823e2f --- /dev/null +++ b/src/test/common/test_lruset.cc @@ -0,0 +1,109 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2013 Inktank <info@inktank.com> + * + * LGPL-2.1 (see COPYING-LGPL2.1) or later + */ + +#include <iostream> +#include <gtest/gtest.h> + +#include "common/LRUSet.h" + +struct thing { + int a; + thing(int i) : a(i) {} + friend bool operator==(const thing &a, const thing &b) { + return a.a == b.a; + } + friend std::size_t hash_value(const thing &value) { + return value.a; + } +}; + +namespace std { + template<> struct hash<thing> { + size_t operator()(const thing& r) const { + return r.a; + } + }; +} + + +TEST(LRUSet, insert_complex) { + LRUSet<thing> s; + s.insert(thing(1)); + s.insert(thing(2)); + + ASSERT_TRUE(s.contains(thing(1))); + ASSERT_TRUE(s.contains(thing(2))); + ASSERT_FALSE(s.contains(thing(3))); +} + +TEST(LRUSet, insert) { + LRUSet<int> s; + s.insert(1); + s.insert(2); + + ASSERT_TRUE(s.contains(1)); + ASSERT_TRUE(s.contains(2)); + ASSERT_FALSE(s.contains(3)); +} + +TEST(LRUSet, erase) { + LRUSet<int> s; + s.insert(1); + s.insert(2); + s.insert(3); + + s.erase(2); + ASSERT_TRUE(s.contains(1)); + ASSERT_FALSE(s.contains(2)); + ASSERT_TRUE(s.contains(3)); + s.prune(1); + ASSERT_TRUE(s.contains(3)); + ASSERT_FALSE(s.contains(1)); +} + +TEST(LRUSet, prune) { + LRUSet<int> s; + int max = 1000; + for (int i=0; i<max; ++i) { + s.insert(i); + s.prune(max / 10); + } + s.prune(0); + ASSERT_TRUE(s.empty()); +} + +TEST(LRUSet, lru) { + LRUSet<int> s; + s.insert(1); + s.insert(2); + s.insert(3); + s.prune(2); + ASSERT_FALSE(s.contains(1)); + ASSERT_TRUE(s.contains(2)); + ASSERT_TRUE(s.contains(3)); + + s.insert(2); + s.insert(4); + s.prune(2); + ASSERT_FALSE(s.contains(3)); + ASSERT_TRUE(s.contains(2)); + ASSERT_TRUE(s.contains(4)); +} + +TEST(LRUSet, copy) { + LRUSet<int> a, b; + a.insert(1); + b.insert(2); + b.insert(3); + a = b; + ASSERT_FALSE(a.contains(1)); + ASSERT_TRUE(a.contains(2)); + ASSERT_TRUE(a.contains(3)); +} diff --git a/src/test/common/test_mclock_priority_queue.cc b/src/test/common/test_mclock_priority_queue.cc new file mode 100644 index 000000000..8e8bcdf38 --- /dev/null +++ b/src/test/common/test_mclock_priority_queue.cc @@ -0,0 +1,320 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2017 Red Hat Inc. + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <thread> +#include <chrono> +#include <iostream> +#include "gtest/gtest.h" +#include "common/mClockPriorityQueue.h" + + +struct Request { + int value; + Request() : + value(0) + {} + Request(const Request& o) = default; + explicit Request(int value) : + value(value) + {} +}; + + +struct Client { + int client_num; + Client() : + Client(-1) + {} + Client(int client_num) : + client_num(client_num) + {} + friend bool operator<(const Client& r1, const Client& r2) { + return r1.client_num < r2.client_num; + } + friend bool operator==(const Client& r1, const Client& r2) { + return r1.client_num == r2.client_num; + } +}; + + +const crimson::dmclock::ClientInfo* client_info_func(const Client& c) { + static const crimson::dmclock::ClientInfo + the_info(10.0, 10.0, 10.0); + return &the_info; +} + + +TEST(mClockPriorityQueue, Create) +{ + ceph::mClockQueue<Request,Client> q(&client_info_func); +} + + +TEST(mClockPriorityQueue, Sizes) +{ + ceph::mClockQueue<Request,Client> q(&client_info_func); + + ASSERT_TRUE(q.empty()); + ASSERT_EQ(0u, q.get_size_slow()); + + Client c1(1); + Client c2(2); + + q.enqueue_strict(c1, 1, Request(1)); + q.enqueue_strict(c2, 2, Request(2)); + q.enqueue_strict(c1, 2, Request(3)); + q.enqueue(c2, 1, 1u, Request(4)); + q.enqueue(c1, 2, 1u, Request(5)); + q.enqueue_strict(c2, 1, Request(6)); + + ASSERT_FALSE(q.empty()); + ASSERT_EQ(6u, q.get_size_slow()); + + + for (int i = 0; i < 6; ++i) { + (void) q.dequeue(); + } + + ASSERT_TRUE(q.empty()); + ASSERT_EQ(0u, q.get_size_slow()); +} + + +TEST(mClockPriorityQueue, JustStrict) +{ + ceph::mClockQueue<Request,Client> q(&client_info_func); + + Client c1(1); + Client c2(2); + + q.enqueue_strict(c1, 1, Request(1)); + q.enqueue_strict(c2, 2, Request(2)); + q.enqueue_strict(c1, 2, Request(3)); + q.enqueue_strict(c2, 1, Request(4)); + + Request r; + + r = q.dequeue(); + ASSERT_EQ(2, r.value); + r = q.dequeue(); + ASSERT_EQ(3, r.value); + r = q.dequeue(); + ASSERT_EQ(1, r.value); + r = q.dequeue(); + ASSERT_EQ(4, r.value); +} + + +TEST(mClockPriorityQueue, StrictPriorities) +{ + ceph::mClockQueue<Request,Client> q(&client_info_func); + + Client c1(1); + Client c2(2); + + q.enqueue_strict(c1, 1, Request(1)); + q.enqueue_strict(c2, 2, Request(2)); + q.enqueue_strict(c1, 3, Request(3)); + q.enqueue_strict(c2, 4, Request(4)); + + Request r; + + r = q.dequeue(); + ASSERT_EQ(4, r.value); + r = q.dequeue(); + ASSERT_EQ(3, r.value); + r = q.dequeue(); + ASSERT_EQ(2, r.value); + r = q.dequeue(); + ASSERT_EQ(1, r.value); +} + + +TEST(mClockPriorityQueue, JustNotStrict) +{ + ceph::mClockQueue<Request,Client> q(&client_info_func); + + Client c1(1); + Client c2(2); + + // non-strict queue ignores priorites, but will divide between + // clients evenly and maintain orders between clients + q.enqueue(c1, 1, 1u, Request(1)); + q.enqueue(c1, 2, 1u, Request(2)); + q.enqueue(c2, 3, 1u, Request(3)); + q.enqueue(c2, 4, 1u, Request(4)); + + Request r1, r2; + + r1 = q.dequeue(); + ASSERT_TRUE(1 == r1.value || 3 == r1.value); + + r2 = q.dequeue(); + ASSERT_TRUE(1 == r2.value || 3 == r2.value); + + ASSERT_NE(r1.value, r2.value); + + r1 = q.dequeue(); + ASSERT_TRUE(2 == r1.value || 4 == r1.value); + + r2 = q.dequeue(); + ASSERT_TRUE(2 == r2.value || 4 == r2.value); + + ASSERT_NE(r1.value, r2.value); +} + + +TEST(mClockPriorityQueue, EnqueuFront) +{ + ceph::mClockQueue<Request,Client> q(&client_info_func); + + Client c1(1); + Client c2(2); + + // non-strict queue ignores priorites, but will divide between + // clients evenly and maintain orders between clients + q.enqueue(c1, 1, 1u, Request(1)); + q.enqueue(c1, 2, 1u, Request(2)); + q.enqueue(c2, 3, 1u, Request(3)); + q.enqueue(c2, 4, 1u, Request(4)); + q.enqueue_strict(c2, 6, Request(6)); + q.enqueue_strict(c1, 7, Request(7)); + + std::list<Request> reqs; + + for (uint i = 0; i < 4; ++i) { + reqs.emplace_back(q.dequeue()); + } + + for (uint i = 0; i < 4; ++i) { + Request& r = reqs.front(); + if (r.value > 5) { + q.enqueue_strict_front(r.value == 6 ? c2 : 1, r.value, std::move(r)); + } else { + q.enqueue_front(r.value <= 2 ? c1 : c2, r.value, 0, std::move(r)); + } + reqs.pop_front(); + } + + Request r; + + r = q.dequeue(); + ASSERT_EQ(7, r.value); + + r = q.dequeue(); + ASSERT_EQ(6, r.value); + + r = q.dequeue(); + ASSERT_TRUE(1 == r.value || 3 == r.value); + + r = q.dequeue(); + ASSERT_TRUE(1 == r.value || 3 == r.value); + + r = q.dequeue(); + ASSERT_TRUE(2 == r.value || 4 == r.value); + + r = q.dequeue(); + ASSERT_TRUE(2 == r.value || 4 == r.value); +} + + +TEST(mClockPriorityQueue, RemoveByClass) +{ + ceph::mClockQueue<Request,Client> q(&client_info_func); + + Client c1(1); + Client c2(2); + Client c3(3); + + q.enqueue(c1, 1, 1u, Request(1)); + q.enqueue(c2, 1, 1u, Request(2)); + q.enqueue(c3, 1, 1u, Request(4)); + q.enqueue_strict(c1, 2, Request(8)); + q.enqueue_strict(c2, 1, Request(16)); + q.enqueue_strict(c3, 3, Request(32)); + q.enqueue(c3, 1, 1u, Request(64)); + q.enqueue(c2, 1, 1u, Request(128)); + q.enqueue(c1, 1, 1u, Request(256)); + + int out_mask = 2 | 16 | 128; + int in_mask = 1 | 8 | 256; + + std::list<Request> out; + q.remove_by_class(c2, &out); + + ASSERT_EQ(3u, out.size()); + while (!out.empty()) { + ASSERT_TRUE((out.front().value & out_mask) > 0) << + "had value that was not expected after first removal"; + out.pop_front(); + } + + ASSERT_EQ(6u, q.get_size_slow()) << "after removal of three from client c2"; + + q.remove_by_class(c3); + + ASSERT_EQ(3u, q.get_size_slow()) << "after removal of three from client c3"; + while (!q.empty()) { + Request r = q.dequeue(); + ASSERT_TRUE((r.value & in_mask) > 0) << + "had value that was not expected after two removals"; + } +} + + +TEST(mClockPriorityQueue, RemoveByFilter) +{ + ceph::mClockQueue<Request,Client> q(&client_info_func); + + Client c1(1); + Client c2(2); + Client c3(3); + + q.enqueue(c1, 1, 1u, Request(1)); + q.enqueue(c2, 1, 1u, Request(2)); + q.enqueue(c3, 1, 1u, Request(3)); + q.enqueue_strict(c1, 2, Request(4)); + q.enqueue_strict(c2, 1, Request(5)); + q.enqueue_strict(c3, 3, Request(6)); + q.enqueue(c3, 1, 1u, Request(7)); + q.enqueue(c2, 1, 1u, Request(8)); + q.enqueue(c1, 1, 1u, Request(9)); + + std::list<Request> filtered; + + q.remove_by_filter([&](const Request& r) -> bool { + if (r.value & 2) { + filtered.push_back(r); + return true; + } else { + return false; + } + }); + + ASSERT_EQ(4u, filtered.size()) << + "filter should have removed four elements"; + while (!filtered.empty()) { + ASSERT_TRUE((filtered.front().value & 2) > 0) << + "expect this value to have been filtered out"; + filtered.pop_front(); + } + + ASSERT_EQ(5u, q.get_size_slow()) << + "filter should have left five remaining elements"; + while (!q.empty()) { + Request r = q.dequeue(); + ASSERT_TRUE((r.value & 2) == 0) << + "expect this value to have been left in"; + } +} diff --git a/src/test/common/test_mutex_debug.cc b/src/test/common/test_mutex_debug.cc new file mode 100644 index 000000000..977dfe738 --- /dev/null +++ b/src/test/common/test_mutex_debug.cc @@ -0,0 +1,101 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 &smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License version 2, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <future> +#include <mutex> +#include <thread> + +#include "common/mutex_debug.h" + +#include "gtest/gtest.h" + + +template<typename Mutex> +static bool test_try_lock(Mutex* m) { + if (!m->try_lock()) + return false; + m->unlock(); + return true; +} + +template<typename Mutex> +static void test_lock() { + Mutex m("mutex"); + auto ttl = &test_try_lock<Mutex>; + + m.lock(); + ASSERT_TRUE(m.is_locked()); + auto f1 = std::async(std::launch::async, ttl, &m); + ASSERT_FALSE(f1.get()); + + ASSERT_TRUE(m.is_locked()); + ASSERT_TRUE(!!m); + + m.unlock(); + ASSERT_FALSE(m.is_locked()); + ASSERT_FALSE(!!m); + + auto f3 = std::async(std::launch::async, ttl, &m); + ASSERT_TRUE(f3.get()); + + ASSERT_FALSE(m.is_locked()); + ASSERT_FALSE(!!m); +} + +TEST(MutexDebug, Lock) { + test_lock<ceph::mutex_debug>(); +} + +TEST(MutexDebug, NotRecursive) { + ceph::mutex_debug m("foo"); + auto ttl = &test_try_lock<mutex_debug>; + + ASSERT_NO_THROW(m.lock()); + ASSERT_TRUE(m.is_locked()); + ASSERT_FALSE(std::async(std::launch::async, ttl, &m).get()); + + ASSERT_THROW(m.lock(), std::system_error); + ASSERT_TRUE(m.is_locked()); + ASSERT_FALSE(std::async(std::launch::async, ttl, &m).get()); + + ASSERT_NO_THROW(m.unlock()); + ASSERT_FALSE(m.is_locked()); + ASSERT_TRUE(std::async(std::launch::async, ttl, &m).get()); +} + +TEST(MutexRecursiveDebug, Lock) { + test_lock<ceph::mutex_recursive_debug>(); +} + + +TEST(MutexRecursiveDebug, Recursive) { + ceph::mutex_recursive_debug m("m"); + auto ttl = &test_try_lock<mutex_recursive_debug>; + + ASSERT_NO_THROW(m.lock()); + ASSERT_TRUE(m.is_locked()); + ASSERT_FALSE(std::async(std::launch::async, ttl, &m).get()); + + ASSERT_NO_THROW(m.lock()); + ASSERT_TRUE(m.is_locked()); + ASSERT_FALSE(std::async(std::launch::async, ttl, &m).get()); + + ASSERT_NO_THROW(m.unlock()); + ASSERT_TRUE(m.is_locked()); + ASSERT_FALSE(std::async(std::launch::async, ttl, &m).get()); + + ASSERT_NO_THROW(m.unlock()); + ASSERT_FALSE(m.is_locked()); + ASSERT_TRUE(std::async(std::launch::async, ttl, &m).get()); +} diff --git a/src/test/common/test_numa.cc b/src/test/common/test_numa.cc new file mode 100644 index 000000000..a3bbdf223 --- /dev/null +++ b/src/test/common/test_numa.cc @@ -0,0 +1,72 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "gtest/gtest.h" +#include "common/numa.h" + +TEST(cpu_set, parse_list) { + cpu_set_t cpu_set; + size_t size; + + ASSERT_EQ(0, parse_cpu_set_list("0-3", &size, &cpu_set)); + ASSERT_EQ(size, 4u); + for (unsigned i = 0; i < size; ++i) { + ASSERT_TRUE(CPU_ISSET(i, &cpu_set)); + } + + ASSERT_EQ(0, parse_cpu_set_list("0-3,6-7", &size, &cpu_set)); + ASSERT_EQ(size, 8u); + for (unsigned i = 0; i < 4; ++i) { + ASSERT_TRUE(CPU_ISSET(i, &cpu_set)); + } + for (unsigned i = 4; i < 6; ++i) { + ASSERT_FALSE(CPU_ISSET(i, &cpu_set)); + } + for (unsigned i = 6; i < 8; ++i) { + ASSERT_TRUE(CPU_ISSET(i, &cpu_set)); + } + + ASSERT_EQ(0, parse_cpu_set_list("0-31", &size, &cpu_set)); + ASSERT_EQ(size, 32u); + for (unsigned i = 0; i < size; ++i) { + ASSERT_TRUE(CPU_ISSET(i, &cpu_set)); + } +} + +TEST(cpu_set, to_str_list) { + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + CPU_SET(0, &cpu_set); + ASSERT_EQ(std::string("0"), cpu_set_to_str_list(8, &cpu_set)); + CPU_SET(1, &cpu_set); + CPU_SET(2, &cpu_set); + CPU_SET(3, &cpu_set); + ASSERT_EQ(std::string("0-3"), cpu_set_to_str_list(8, &cpu_set)); + CPU_SET(5, &cpu_set); + ASSERT_EQ(std::string("0-3,5"), cpu_set_to_str_list(8, &cpu_set)); + CPU_SET(6, &cpu_set); + CPU_SET(7, &cpu_set); + ASSERT_EQ(std::string("0-3,5-7"), cpu_set_to_str_list(8, &cpu_set)); +} + +TEST(cpu_set, round_trip_list) +{ + for (unsigned i = 0; i < 100; ++i) { + cpu_set_t cpu_set; + size_t size = 32; + CPU_ZERO(&cpu_set); + for (unsigned i = 0; i < 32; ++i) { + if (rand() % 1) { + CPU_SET(i, &cpu_set); + } + } + std::string v = cpu_set_to_str_list(size, &cpu_set); + cpu_set_t cpu_set_2; + size_t size2; + ASSERT_EQ(0, parse_cpu_set_list(v.c_str(), &size2, &cpu_set_2)); + for (unsigned i = 0; i < 32; ++i) { + ASSERT_TRUE(CPU_ISSET(i, &cpu_set) == CPU_ISSET(i, &cpu_set_2)); + } + } +} + diff --git a/src/test/common/test_option.cc b/src/test/common/test_option.cc new file mode 100644 index 000000000..a75f74dc9 --- /dev/null +++ b/src/test/common/test_option.cc @@ -0,0 +1,73 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- +// vim: ts=8 sw=2 smarttab expandtab + +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +#include <gtest/gtest.h> + +#include "common/options.h" + +using namespace std; + +TEST(Option, validate_min_max) +{ + auto opt = Option{"foo", Option::TYPE_MILLISECS, Option::LEVEL_ADVANCED} + .set_default(42) + .set_min_max(10, 128); + struct test_t { + unsigned new_val; + int expected_retval; + }; + test_t tests[] = + {{9, -EINVAL}, + {10, 0}, + {11, 0}, + {128, 0}, + {1024, -EINVAL} + }; + for (auto& test : tests) { + Option::value_t new_value = std::chrono::milliseconds{test.new_val}; + std::string err; + GTEST_ASSERT_EQ(test.expected_retval, opt.validate(new_value, &err)); + } +} + +TEST(Option, parse) +{ + auto opt = Option{"foo", Option::TYPE_MILLISECS, Option::LEVEL_ADVANCED} + .set_default(42) + .set_min_max(10, 128); + struct test_t { + string new_val; + int expected_retval; + unsigned expected_parsed_val; + }; + test_t tests[] = + {{"9", -EINVAL, 0}, + {"10", 0, 10}, + {"11", 0, 11}, + {"128", 0, 128}, + {"1024", -EINVAL, 0} + }; + for (auto& test : tests) { + Option::value_t parsed_val; + std::string err; + GTEST_ASSERT_EQ(test.expected_retval, + opt.parse_value(test.new_val, &parsed_val, &err)); + if (test.expected_retval == 0) { + Option::value_t expected_parsed_val = + std::chrono::milliseconds{test.expected_parsed_val}; + GTEST_ASSERT_EQ(parsed_val, expected_parsed_val); + } + } +} + +/* + * Local Variables: + * compile-command: "cd ../../../build ; + * ninja unittest_option && bin/unittest_option + * " + * End: + */ diff --git a/src/test/common/test_perf_counters_key.cc b/src/test/common/test_perf_counters_key.cc new file mode 100644 index 000000000..5a156c749 --- /dev/null +++ b/src/test/common/test_perf_counters_key.cc @@ -0,0 +1,129 @@ +#include "common/perf_counters_key.h" +#include <gtest/gtest.h> + +namespace ceph::perf_counters { + +TEST(PerfCounters, key_create) +{ + EXPECT_EQ(key_create(""), + std::string_view("\0", 1)); + + EXPECT_EQ(key_create("perf"), + std::string_view("perf\0", 5)); + + EXPECT_EQ(key_create("perf", {{"",""}}), + std::string_view("perf\0\0\0", 7)); + + EXPECT_EQ(key_create("perf", {{"","a"}, {"",""}}), + std::string_view("perf\0\0a\0", 8)); + + EXPECT_EQ(key_create("perf", {{"a","b"}}), + std::string_view("perf\0a\0b\0", 9)); + + EXPECT_EQ(key_create("perf", {{"y","z"}, {"a","b"}}), + std::string_view("perf\0a\0b\0y\0z\0", 13)); + + EXPECT_EQ(key_create("perf", {{"a","b"}, {"a","c"}}), + std::string_view("perf\0a\0b\0", 9)); + + EXPECT_EQ(key_create("perf", {{"a","z"}, {"a","b"}}), + std::string_view("perf\0a\0z\0", 9)); + + EXPECT_EQ(key_create("perf", {{"d",""}, {"c",""}, {"b",""}, {"a",""}}), + std::string_view("perf\0a\0\0b\0\0c\0\0d\0\0", 17)); +} + +TEST(PerfCounters, key_insert) +{ + EXPECT_EQ(key_insert("", {{"",""}}), + std::string_view("\0\0\0", 3)); + + EXPECT_EQ(key_insert("", {{"",""}, {"",""}}), + std::string_view("\0\0\0", 3)); + + EXPECT_EQ(key_insert(std::string_view{"\0\0\0", 3}, {{"",""}}), + std::string_view("\0\0\0", 3)); + + EXPECT_EQ(key_insert(std::string_view{"\0", 1}, {{"",""}}), + std::string_view("\0\0\0", 3)); + + EXPECT_EQ(key_insert("", {{"a","b"}}), + std::string_view("\0a\0b\0", 5)); + + EXPECT_EQ(key_insert(std::string_view{"\0", 1}, {{"a","b"}}), + std::string_view("\0a\0b\0", 5)); + + EXPECT_EQ(key_insert("a", {{"",""}}), + std::string_view("a\0\0\0", 4)); + + EXPECT_EQ(key_insert(std::string_view{"a\0", 2}, {{"",""}}), + std::string_view("a\0\0\0", 4)); + + EXPECT_EQ(key_insert(std::string_view{"p\0", 2}, {{"a","b"}}), + std::string_view("p\0a\0b\0", 6)); + + EXPECT_EQ(key_insert(std::string_view{"p\0a\0a\0", 6}, {{"a","b"}}), + std::string_view("p\0a\0b\0", 6)); + + EXPECT_EQ(key_insert(std::string_view{"p\0a\0z\0", 6}, {{"a","b"}}), + std::string_view("p\0a\0b\0", 6)); + + EXPECT_EQ(key_insert(std::string_view{"p\0z\0z\0", 6}, {{"a","b"}}), + std::string_view("p\0a\0b\0z\0z\0", 10)); + + EXPECT_EQ(key_insert(std::string_view{"p\0b\0b\0", 6}, + {{"a","a"}, {"c","c"}}), + std::string_view("p\0a\0a\0b\0b\0c\0c\0", 14)); + + EXPECT_EQ(key_insert(std::string_view{"p\0a\0a\0b\0b\0c\0c\0", 14}, + {{"z","z"}, {"b","z"}}), + std::string_view("p\0a\0a\0b\0z\0c\0c\0z\0z\0", 18)); +} + +TEST(PerfCounters, key_name) +{ + EXPECT_EQ(key_name(""), + ""); + EXPECT_EQ(key_name({"\0", 1}), + ""); + EXPECT_EQ(key_name({"perf\0", 5}), + "perf"); + EXPECT_EQ(key_name({"perf\0\0\0", 7}), + "perf"); +} + +TEST(PerfCounters, key_labels) +{ + { + auto labels = key_labels(""); + EXPECT_EQ(labels.begin(), labels.end()); + } + { + auto labels = key_labels({"\0", 1}); + EXPECT_EQ(labels.begin(), labels.end()); + } + { + auto labels = key_labels({"perf\0", 5}); + EXPECT_EQ(labels.begin(), labels.end()); + } + { + auto labels = key_labels({"\0\0\0", 3}); + ASSERT_EQ(1, std::distance(labels.begin(), labels.end())); + EXPECT_EQ(label_pair("", ""), *labels.begin()); + } + { + auto labels = key_labels({"\0a\0b\0", 5}); + ASSERT_EQ(1, std::distance(labels.begin(), labels.end())); + EXPECT_EQ(label_pair("a", "b"), *labels.begin()); + EXPECT_EQ(std::next(labels.begin()), labels.end()); + } + { + auto labels = key_labels({"\0a\0b\0c\0d\0", 9}); + ASSERT_EQ(2, std::distance(labels.begin(), labels.end())); + EXPECT_EQ(label_pair("a", "b"), *labels.begin()); + EXPECT_EQ(label_pair("c", "d"), *std::next(labels.begin())); + EXPECT_EQ(std::next(labels.begin(), 2), labels.end()); + } +} + +} // namespace ceph::perf_counters diff --git a/src/test/common/test_perf_histogram.cc b/src/test/common/test_perf_histogram.cc new file mode 100644 index 000000000..4ca8d687a --- /dev/null +++ b/src/test/common/test_perf_histogram.cc @@ -0,0 +1,247 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 &smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2017 OVH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License version 2, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "common/perf_histogram.h" + +#include "gtest/gtest.h" + +template <int DIM> +class PerfHistogramAccessor : public PerfHistogram<DIM> { +public: + typedef PerfHistogram<DIM> Base; + + using Base::PerfHistogram; + + static int64_t get_bucket_for_axis( + int64_t value, const PerfHistogramCommon::axis_config_d& axis_config) { + return Base::get_bucket_for_axis(value, axis_config); + } + + static std::vector<std::pair<int64_t, int64_t>> get_axis_bucket_ranges( + const PerfHistogramCommon::axis_config_d& axis_config) { + return Base::get_axis_bucket_ranges(axis_config); + } + + const typename Base::axis_config_d& get_axis_config(int num) { + return Base::m_axes_config[num]; + } + + template <typename F1, typename F2, typename F3> + void visit_values(F1 f1, F2 f2, F3 f3) { + Base::visit_values(f1, f2, f3); + } +}; + +TEST(PerfHistogram, GetBucketForAxis) { + PerfHistogramCommon::axis_config_d linear{ + "", PerfHistogramCommon::SCALE_LINEAR, 100, 3, 4}; + + ASSERT_EQ(0, PerfHistogramAccessor<1>::get_bucket_for_axis(-1, linear)); + ASSERT_EQ(0, PerfHistogramAccessor<1>::get_bucket_for_axis(0, linear)); + ASSERT_EQ(0, PerfHistogramAccessor<1>::get_bucket_for_axis(99, linear)); + ASSERT_EQ(1, PerfHistogramAccessor<1>::get_bucket_for_axis(100, linear)); + ASSERT_EQ(1, PerfHistogramAccessor<1>::get_bucket_for_axis(101, linear)); + ASSERT_EQ(1, PerfHistogramAccessor<1>::get_bucket_for_axis(102, linear)); + ASSERT_EQ(2, PerfHistogramAccessor<1>::get_bucket_for_axis(103, linear)); + ASSERT_EQ(2, PerfHistogramAccessor<1>::get_bucket_for_axis(105, linear)); + ASSERT_EQ(3, PerfHistogramAccessor<1>::get_bucket_for_axis(106, linear)); + ASSERT_EQ(3, PerfHistogramAccessor<1>::get_bucket_for_axis(108, linear)); + ASSERT_EQ(3, PerfHistogramAccessor<1>::get_bucket_for_axis(109, linear)); + + ASSERT_EQ(0, PerfHistogramAccessor<1>::get_bucket_for_axis( + std::numeric_limits<int64_t>::min(), linear)); + ASSERT_EQ(3, PerfHistogramAccessor<1>::get_bucket_for_axis( + std::numeric_limits<int64_t>::max(), linear)); + + PerfHistogramCommon::axis_config_d logarithmic{ + "", PerfHistogramCommon::SCALE_LOG2, 100, 3, 5}; + + ASSERT_EQ(0, PerfHistogramAccessor<1>::get_bucket_for_axis(-1, logarithmic)); + ASSERT_EQ(0, PerfHistogramAccessor<1>::get_bucket_for_axis(0, logarithmic)); + ASSERT_EQ(0, PerfHistogramAccessor<1>::get_bucket_for_axis(99, logarithmic)); + ASSERT_EQ(1, PerfHistogramAccessor<1>::get_bucket_for_axis(100, logarithmic)); + ASSERT_EQ(1, PerfHistogramAccessor<1>::get_bucket_for_axis(101, logarithmic)); + ASSERT_EQ(1, PerfHistogramAccessor<1>::get_bucket_for_axis(102, logarithmic)); + ASSERT_EQ(2, PerfHistogramAccessor<1>::get_bucket_for_axis(103, logarithmic)); + ASSERT_EQ(2, PerfHistogramAccessor<1>::get_bucket_for_axis(105, logarithmic)); + ASSERT_EQ(3, PerfHistogramAccessor<1>::get_bucket_for_axis(106, logarithmic)); + ASSERT_EQ(3, PerfHistogramAccessor<1>::get_bucket_for_axis(111, logarithmic)); + ASSERT_EQ(4, PerfHistogramAccessor<1>::get_bucket_for_axis(112, logarithmic)); + ASSERT_EQ(4, PerfHistogramAccessor<1>::get_bucket_for_axis(124, logarithmic)); + + ASSERT_EQ(0, PerfHistogramAccessor<1>::get_bucket_for_axis( + std::numeric_limits<int64_t>::min(), logarithmic)); + ASSERT_EQ(4, PerfHistogramAccessor<1>::get_bucket_for_axis( + std::numeric_limits<int64_t>::max(), logarithmic)); +} + +static const int XS = 5; +static const int YS = 7; + +static const auto x_axis = PerfHistogramCommon::axis_config_d{ + "x", PerfHistogramCommon::SCALE_LINEAR, 0, 1, XS}; +static const auto y_axis = PerfHistogramCommon::axis_config_d{ + "y", PerfHistogramCommon::SCALE_LOG2, 0, 1, YS}; + +TEST(PerfHistogram, ZeroedInitially) { + PerfHistogramAccessor<2> h{x_axis, y_axis}; + for (int x = 0; x < XS; ++x) { + for (int y = 0; y < YS; ++y) { + ASSERT_EQ(0UL, h.read_bucket(x, y)); + } + } +} + +TEST(PerfHistogram, Copy) { + PerfHistogramAccessor<2> h1{x_axis, y_axis}; + h1.inc_bucket(1, 1); + h1.inc_bucket(2, 3); + h1.inc_bucket(4, 5); + + PerfHistogramAccessor<2> h2 = h1; + + const int cx = 1; + const int cy = 2; + + h1.inc_bucket(cx, cy); + + // Axes configuration must be equal + for (int i = 0; i < 2; i++) { + const auto& ac1 = h1.get_axis_config(i); + const auto& ac2 = h2.get_axis_config(i); + ASSERT_EQ(ac1.m_name, ac2.m_name); + ASSERT_EQ(ac1.m_scale_type, ac2.m_scale_type); + ASSERT_EQ(ac1.m_min, ac2.m_min); + ASSERT_EQ(ac1.m_quant_size, ac2.m_quant_size); + ASSERT_EQ(ac1.m_buckets, ac2.m_buckets); + } + + // second histogram must have histogram values equal to the first + // one at the time of copy + for (int x = 0; x < XS; x++) { + for (int y = 0; y < YS; y++) { + if (x == cx && y == cy) { + ASSERT_NE(h1.read_bucket(x, y), h2.read_bucket(x, y)); + } else { + ASSERT_EQ(h1.read_bucket(x, y), h2.read_bucket(x, y)); + } + } + } +} + +TEST(PerfHistogram, SimpleValues) { + PerfHistogramAccessor<2> h{x_axis, y_axis}; + ASSERT_EQ(0UL, h.read_bucket(1, 1)); + h.inc(0, 0); + ASSERT_EQ(1UL, h.read_bucket(1, 1)); + + ASSERT_EQ(0UL, h.read_bucket(2, 2)); + h.inc(1, 1); + ASSERT_EQ(1UL, h.read_bucket(2, 2)); + + ASSERT_EQ(0UL, h.read_bucket(3, 3)); + h.inc(2, 2); + ASSERT_EQ(1UL, h.read_bucket(3, 3)); + + ASSERT_EQ(0UL, h.read_bucket(4, 3)); + h.inc(3, 3); + ASSERT_EQ(1UL, h.read_bucket(4, 3)); +} + +TEST(PerfHistogram, OneBucketRange) { + auto ranges = PerfHistogramAccessor<1>::get_axis_bucket_ranges( + PerfHistogramCommon::axis_config_d{"", PerfHistogramCommon::SCALE_LINEAR, + 0, 1, 1}); + + ASSERT_EQ(1UL, ranges.size()); + ASSERT_EQ(std::numeric_limits<int64_t>::min(), ranges[0].first); + ASSERT_EQ(std::numeric_limits<int64_t>::max(), ranges[0].second); +} + +TEST(PerfHistogram, TwoBucketRange) { + auto ranges = PerfHistogramAccessor<1>::get_axis_bucket_ranges( + PerfHistogramCommon::axis_config_d{"", PerfHistogramCommon::SCALE_LINEAR, + 0, 1, 2}); + + ASSERT_EQ(2UL, ranges.size()); + ASSERT_EQ(std::numeric_limits<int64_t>::min(), ranges[0].first); + ASSERT_EQ(-1, ranges[0].second); + ASSERT_EQ(0, ranges[1].first); + ASSERT_EQ(std::numeric_limits<int64_t>::max(), ranges[1].second); +} + +TEST(PerfHistogram, LinearBucketRange) { + PerfHistogramCommon::axis_config_d ac{"", PerfHistogramCommon::SCALE_LINEAR, + 100, 10, 15}; + auto ranges = PerfHistogramAccessor<1>::get_axis_bucket_ranges(ac); + + for (size_t i = 0; i < ranges.size(); ++i) { + ASSERT_EQ( + static_cast<long>(i), PerfHistogramAccessor<1>::get_bucket_for_axis(ranges[i].first, ac)); + ASSERT_EQ( + static_cast<long>(i), PerfHistogramAccessor<1>::get_bucket_for_axis(ranges[i].second, ac)); + } + + for (size_t i = 1; i < ranges.size(); ++i) { + ASSERT_EQ(ranges[i].first, ranges[i - 1].second + 1); + } +} + +TEST(PerfHistogram, LogarithmicBucketRange) { + PerfHistogramCommon::axis_config_d ac{"", PerfHistogramCommon::SCALE_LOG2, + 100, 10, 15}; + auto ranges = PerfHistogramAccessor<1>::get_axis_bucket_ranges(ac); + + for (size_t i = 0; i < ranges.size(); ++i) { + ASSERT_EQ( + static_cast<long>(i), PerfHistogramAccessor<1>::get_bucket_for_axis(ranges[i].first, ac)); + ASSERT_EQ( + static_cast<long>(i), PerfHistogramAccessor<1>::get_bucket_for_axis(ranges[i].second, ac)); + } + + for (size_t i = 1; i < ranges.size(); ++i) { + ASSERT_EQ(ranges[i].first, ranges[i - 1].second + 1); + } +} + +TEST(PerfHistogram, AxisAddressing) { + PerfHistogramCommon::axis_config_d ac1{"", PerfHistogramCommon::SCALE_LINEAR, + 0, 1, 7}; + PerfHistogramCommon::axis_config_d ac2{"", PerfHistogramCommon::SCALE_LINEAR, + 0, 1, 9}; + PerfHistogramCommon::axis_config_d ac3{"", PerfHistogramCommon::SCALE_LINEAR, + 0, 1, 11}; + + PerfHistogramAccessor<3> h{ac1, ac2, ac3}; + + h.inc(1, 2, 3); // Should end up in buckets 2, 3, 4 + h.inc_bucket(4, 5, 6); + + std::vector<int64_t> rawValues; + h.visit_values([](int) {}, + [&rawValues](int64_t value) { rawValues.push_back(value); }, + [](int) {}); + + for (size_t i = 0; i < rawValues.size(); ++i) { + switch (i) { + case 4 + 11 * (3 + 9 * 2): + case 6 + 11 * (5 + 9 * 4): + ASSERT_EQ(1, rawValues[i]); + break; + default: + ASSERT_EQ(0, rawValues[i]); + break; + } + } +} diff --git a/src/test/common/test_pretty_binary.cc b/src/test/common/test_pretty_binary.cc new file mode 100644 index 000000000..837dbefc1 --- /dev/null +++ b/src/test/common/test_pretty_binary.cc @@ -0,0 +1,50 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "common/pretty_binary.h" + +#include "gtest/gtest.h" + +TEST(pretty_binary, print) { + ASSERT_EQ(pretty_binary_string(std::string("foo\001\002bars")), std::string("'foo'0x0102'bars'")); + ASSERT_EQ(pretty_binary_string(std::string("foo''bars")), std::string("'foo''''bars'")); + ASSERT_EQ(pretty_binary_string(std::string("foo\377 \200!!")), std::string("'foo'0xFF2020802121")); + ASSERT_EQ(pretty_binary_string(std::string("\001\002\003\004")), std::string("0x01020304")); +} + +TEST(pretty_binary, unprint) { + ASSERT_EQ(pretty_binary_string_reverse(std::string("'foo'0x0102'bars'")), std::string("foo\001\002bars")); + ASSERT_EQ(pretty_binary_string_reverse(std::string("'foo''''bars'")), std::string("foo''bars")); + ASSERT_EQ(pretty_binary_string_reverse(std::string("'foo'0xFF2020802121")), std::string("foo\377 \200!!")); + ASSERT_EQ(pretty_binary_string_reverse(std::string("0x01020304")),std::string("\001\002\003\004")); +} + +TEST(pretty_binary, all_chars) { + std::string a; + for (unsigned j = 0; j < 256; ++j) { + a.push_back((char)j); + } + std::string b = pretty_binary_string(a); + ASSERT_EQ(a, pretty_binary_string_reverse(b)); +} + +TEST(pretty_binary, random) { + for (size_t i = 0; i < 100000; i++) { + std::string a; + size_t r = rand() % 100; + for (size_t j = 0; j < r; ++j) { + a.push_back((unsigned char)rand()); + } + std::string b = pretty_binary_string(a); + ASSERT_EQ(a, pretty_binary_string_reverse(b)); + } +} + +TEST(pretty_binary, invalid) { + ASSERT_THROW(pretty_binary_string_reverse("'"), std::invalid_argument); + ASSERT_THROW(pretty_binary_string_reverse("0x1"), std::invalid_argument); + ASSERT_THROW(pretty_binary_string_reverse("0x"), std::invalid_argument); + ASSERT_THROW(pretty_binary_string_reverse("'''"), std::invalid_argument); + ASSERT_THROW(pretty_binary_string_reverse("'a'x"), std::invalid_argument); + ASSERT_THROW(pretty_binary_string_reverse("'a'0x"), std::invalid_argument); +} diff --git a/src/test/common/test_prioritized_queue.cc b/src/test/common/test_prioritized_queue.cc new file mode 100644 index 000000000..83bd41f8c --- /dev/null +++ b/src/test/common/test_prioritized_queue.cc @@ -0,0 +1,206 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "gtest/gtest.h" +#include "common/PrioritizedQueue.h" + +#include <numeric> +#include <vector> +#include <algorithm> +#include <random> + +using std::vector; + +class PrioritizedQueueTest : public testing::Test +{ +protected: + typedef int Klass; + typedef unsigned Item; + typedef PrioritizedQueue<Item, Klass> PQ; + enum { item_size = 100, }; + vector<Item> items; + + void SetUp() override { + for (int i = 0; i < item_size; i++) { + items.push_back(Item(i)); + } + std::random_device rd; + std::default_random_engine rng(rd()); + std::shuffle(items.begin(), items.end(), rng); + } + void TearDown() override { + items.clear(); + } +}; + +TEST_F(PrioritizedQueueTest, capacity) { + const unsigned min_cost = 10; + const unsigned max_tokens_per_subqueue = 50; + PQ pq(max_tokens_per_subqueue, min_cost); + EXPECT_TRUE(pq.empty()); + EXPECT_EQ(0u, pq.length()); + + pq.enqueue_strict(Klass(1), 0, Item(0)); + EXPECT_FALSE(pq.empty()); + EXPECT_EQ(1u, pq.length()); + + for (int i = 0; i < 3; i++) { + pq.enqueue(Klass(1), 0, 10, Item(0)); + } + for (unsigned i = 4; i > 0; i--) { + EXPECT_FALSE(pq.empty()); + EXPECT_EQ(i, pq.length()); + pq.dequeue(); + } + EXPECT_TRUE(pq.empty()); + EXPECT_EQ(0u, pq.length()); +} + +TEST_F(PrioritizedQueueTest, strict_pq) { + const unsigned min_cost = 1; + const unsigned max_tokens_per_subqueue = 50; + PQ pq(max_tokens_per_subqueue, min_cost); + // 0 .. item_size-1 + for (unsigned i = 0; i < item_size; i++) { + unsigned priority = items[i]; + const Item& item = items[i]; + pq.enqueue_strict(Klass(0), priority, Item(item)); + } + // item_size-1 .. 0 + for (unsigned i = item_size; i > 0; i--) { + Item item = pq.dequeue(); + unsigned priority = item; + EXPECT_EQ(i - 1, priority); + } +} + +TEST_F(PrioritizedQueueTest, lowest_among_eligible_otherwise_highest) { + // to minimize the effect of `distribute_tokens()` + // all eligible items will be assigned with cost of min_cost + const unsigned min_cost = 0; + const unsigned max_tokens_per_subqueue = 100; + PQ pq(max_tokens_per_subqueue, min_cost); + +#define ITEM_TO_COST(item_) (item_ % 5 ? min_cost : max_tokens_per_subqueue) + unsigned num_low_cost = 0, num_high_cost = 0; + for (int i = 0; i < item_size; i++) { + const Item& item = items[i]; + unsigned cost = ITEM_TO_COST(item); + unsigned priority = item; + if (cost == min_cost) { + num_low_cost++; + } else { + num_high_cost++; + } + pq.enqueue(Klass(0), priority, cost, Item(item)); + } + // the token in all buckets is 0 at the beginning, so dequeue() should pick + // the first one with the highest priority. + unsigned highest_priority; + { + Item item = pq.dequeue(); + unsigned cost = ITEM_TO_COST(item); + unsigned priority = item; + if (cost == min_cost) { + num_low_cost--; + } else { + num_high_cost--; + } + EXPECT_EQ(item_size - 1u, priority); + highest_priority = priority; + } + unsigned lowest_priority = 0; + for (unsigned i = 0; i < num_low_cost; i++) { + Item item = pq.dequeue(); + unsigned cost = ITEM_TO_COST(item); + unsigned priority = item; + EXPECT_EQ(min_cost, cost); + EXPECT_GT(priority, lowest_priority); + lowest_priority = priority; + } + for (unsigned i = 0; i < num_high_cost; i++) { + Item item = pq.dequeue(); + unsigned cost = ITEM_TO_COST(item); + unsigned priority = item; + EXPECT_EQ(max_tokens_per_subqueue, cost); + EXPECT_LT(priority, highest_priority); + highest_priority = priority; + } +#undef ITEM_TO_COST +} + +static const unsigned num_classes = 4; +// just a determinitic number +#define ITEM_TO_CLASS(item_) Klass((item_ + 43) % num_classes) + +TEST_F(PrioritizedQueueTest, fairness_by_class) { + // dequeue should be fair to all classes in a certain bucket + const unsigned min_cost = 1; + const unsigned max_tokens_per_subqueue = 50; + PQ pq(max_tokens_per_subqueue, min_cost); + + for (int i = 0; i < item_size; i++) { + const Item& item = items[i]; + Klass k = ITEM_TO_CLASS(item); + unsigned priority = 0; + unsigned cost = 1; + pq.enqueue(k, priority, cost, Item(item)); + } + // just sample first 1/2 of the items + // if i pick too small a dataset, the result won't be statisitcally + // significant. if the sample dataset is too large, it will converge to the + // distribution of the full set. + vector<unsigned> num_picked_in_class(num_classes, 0u); + for (int i = 0; i < item_size / 2; i++) { + Item item = pq.dequeue(); + Klass k = ITEM_TO_CLASS(item); + num_picked_in_class[k]++; + } + unsigned total = std::accumulate(num_picked_in_class.begin(), + num_picked_in_class.end(), + 0); + float avg = float(total) / num_classes; + for (unsigned i = 0; i < num_classes; i++) { + EXPECT_NEAR(avg, num_picked_in_class[i], 0.5); + } +} + + +TEST_F(PrioritizedQueueTest, remove_by_class) { + const unsigned min_cost = 1; + const unsigned max_tokens_per_subqueue = 50; + PQ pq(max_tokens_per_subqueue, min_cost); + const Klass class_to_remove(2); + unsigned num_to_remove = 0; + for (int i = 0; i < item_size; i++) { + const Item& item = items[i]; + Klass k = ITEM_TO_CLASS(item); + pq.enqueue(k, 0, 0, Item(item)); + if (k == class_to_remove) { + num_to_remove++; + } + } + std::list<Item> removed; + pq.remove_by_class(class_to_remove, &removed); + + // see if the removed items are expected ones. + for (std::list<Item>::iterator it = removed.begin(); + it != removed.end(); + ++it) { + const Item& item = *it; + Klass k = ITEM_TO_CLASS(item); + EXPECT_EQ(class_to_remove, k); + items.erase(remove(items.begin(), items.end(), item), items.end()); + } + EXPECT_EQ(num_to_remove, removed.size()); + EXPECT_EQ(item_size - num_to_remove, pq.length()); + EXPECT_EQ(item_size - num_to_remove, items.size()); + // see if the remainder are expeceted also. + while (!pq.empty()) { + const Item item = pq.dequeue(); + Klass k = ITEM_TO_CLASS(item); + EXPECT_NE(class_to_remove, k); + items.erase(remove(items.begin(), items.end(), item), items.end()); + } + EXPECT_TRUE(items.empty()); +} diff --git a/src/test/common/test_rabin_chunk.cc b/src/test/common/test_rabin_chunk.cc new file mode 100644 index 000000000..dc33ba0fe --- /dev/null +++ b/src/test/common/test_rabin_chunk.cc @@ -0,0 +1,151 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include <vector> +#include <cstring> +#include <random> + +#include "include/types.h" +#include "include/buffer.h" + +#include "common/rabin.h" +#include "gtest/gtest.h" + +TEST(Rabin, rabin_hash_simple) { + uint64_t expected = 680425538102669423; + uint64_t result; + + unsigned int window_size = 48; + char data[window_size + 1]; + RabinChunk rabin; + memset(data, 0, window_size + 1); + for (unsigned int i = 0; i < window_size; ++i) { + data[i] = i; + } + result = rabin.gen_rabin_hash(data, 0); + ASSERT_EQ(expected, result); +} + +TEST(Rabin, chunk_check_min_max) { + const char buf[] = "0123456789"; + + bufferlist bl; + RabinChunk rabin; + for (int i = 0; i < 250; i++) { + bl.append(buf); + } + + vector<pair<uint64_t, uint64_t>> chunks; + size_t min_chunk = 2000; + size_t max_chunk = 8000; + + rabin.do_rabin_chunks(bl, chunks, min_chunk, max_chunk); + uint64_t chunk_size = chunks[0].second; + ASSERT_GE(chunk_size , min_chunk); + ASSERT_LE(chunk_size , max_chunk); +} + +TEST(Rabin, test_cdc) { + const char *base_str = "123456789012345678901234567890123456789012345678"; + bufferlist bl, cmp_bl; + for (int i = 0; i < 100; i++) { + bl.append(base_str); + } + cmp_bl.append('a'); + for (int i = 0; i < 100; i++) { + cmp_bl.append(base_str); + } + + RabinChunk rabin; + vector<pair<uint64_t, uint64_t>> chunks; + vector<pair<uint64_t, uint64_t>> cmp_chunks; + size_t min_chunk = 200; + size_t max_chunk = 800; + rabin.do_rabin_chunks(bl, chunks, min_chunk, max_chunk); + rabin.do_rabin_chunks(cmp_bl, cmp_chunks, min_chunk, max_chunk); + // offset, len will be the same, except in the case of first offset + ASSERT_EQ(chunks[4].first + 1, cmp_chunks[4].first); + ASSERT_EQ(chunks[4].second, cmp_chunks[4].second); +} + +void generate_buffer(int size, bufferlist *outbl) +{ + outbl->clear(); + outbl->append_zero(size); + char *b = outbl->c_str(); + std::mt19937_64 engine; + for (size_t i = 0; i < size / sizeof(uint64_t); ++i) { + ((uint64_t*)b)[i] = engine(); + } +} + +#if 0 +this fails! +TEST(Rabin, shifts) +{ + RabinChunk rabin; + rabin.set_target_bits(18, 2); + + for (int frontlen = 1; frontlen < 5; frontlen++) { + bufferlist bl1, bl2; + generate_buffer(4*1024*1024, &bl1); + generate_buffer(frontlen, &bl2); + bl2.append(bl1); + bl2.rebuild(); + + vector<pair<uint64_t, uint64_t>> chunks1, chunks2; + rabin.do_rabin_chunks(bl1, chunks1); + rabin.do_rabin_chunks(bl2, chunks2); + cout << "1: " << chunks1 << std::endl; + cout << "2: " << chunks2 << std::endl; + + ASSERT_GE(chunks2.size(), chunks1.size()); + int match = 0; + for (unsigned i = 0; i < chunks1.size(); ++i) { + unsigned j = i + (chunks2.size() - chunks1.size()); + if (chunks1[i].first + frontlen == chunks2[j].first && + chunks1[i].second == chunks2[j].second) { + match++; + } + } + ASSERT_GE(match, chunks1.size() - 1); + } +} +#endif + +void do_size_histogram(RabinChunk& rabin, bufferlist& bl, + map<int,int> *h) +{ + vector<pair<uint64_t, uint64_t>> chunks; + rabin.do_rabin_chunks(bl, chunks); + for (auto& i : chunks) { + unsigned b = i.second & 0xfffff000; + //unsigned b = 1 << cbits(i.second); + (*h)[b]++; + } +} + +void print_histogram(map<int,int>& h) +{ + cout << "size\tcount" << std::endl; + for (auto i : h) { + cout << i.first << "\t" << i.second << std::endl; + } +} + +TEST(Rabin, chunk_random) +{ + RabinChunk rabin; + rabin.set_target_bits(18, 2); + + map<int,int> h; + for (int i = 0; i < 8; ++i) { + cout << "."; + cout.flush(); + bufferlist r; + generate_buffer(16*1024*1024, &r); + do_size_histogram(rabin, r, &h); + } + cout << std::endl; + print_histogram(h); +} diff --git a/src/test/common/test_random.cc b/src/test/common/test_random.cc new file mode 100644 index 000000000..490f8d547 --- /dev/null +++ b/src/test/common/test_random.cc @@ -0,0 +1,246 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2017 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * +*/ + +#include <sstream> + +#include "include/random.h" + +#include "gtest/gtest.h" + +// Helper to see if calls compile with various types: +template <typename T> +T type_check_ok(const T min, const T max) +{ + return ceph::util::generate_random_number(min, max); +} + +/* Help wrangle "unused variable" warnings: */ +template <typename X> +void swallow_values(const X x) +{ + static_cast<void>(x); +} + +template <typename X, typename ...XS> +void swallow_values(const X x, const XS... xs) +{ + swallow_values(x), swallow_values(xs...); +} + +// Mini-examples showing canonical usage: +TEST(util, test_random_canonical) +{ + // Seed random number generation: + ceph::util::randomize_rng(); + + // Get a random int between 0 and max int: + auto a = ceph::util::generate_random_number(); + + // Get a random int between 0 and 20: + auto b = ceph::util::generate_random_number(20); + + // Get a random int between 1 and 20: + auto c = ceph::util::generate_random_number(1, 20); + + // Get a random float between 0.0 and 20.0: + auto d = ceph::util::generate_random_number(20.0); + + // Get a random float between 0.001 and 0.991: + auto e = ceph::util::generate_random_number(0.001, 0.991); + + // Make a function object RNG suitable for putting on its own thread: + auto gen_fn = ceph::util::random_number_generator<int>(); + auto z = gen_fn(); + gen_fn.seed(42); // re-seed + + // Placate the compiler: + swallow_values(a, b, c, d, e, z); +} + +TEST(util, test_random) +{ + /* The intent of this test is not to formally test random number generation, + but rather to casually check that "it works" and catch regressions: */ + + // The default overload should compile: + ceph::util::randomize_rng(); + + { + int a = ceph::util::generate_random_number(); + int b = ceph::util::generate_random_number(); + + /* Technically, this can still collide and cause a false negative, but let's + be optimistic: */ + if (std::numeric_limits<int>::max() > 32767) { + ASSERT_NE(a, b); + } + } + + // Check that the nullary version accepts different numeric types: + { + long def = ceph::util::generate_random_number(); + long l = ceph::util::generate_random_number<long>(); + int64_t i = ceph::util::generate_random_number<int64_t>(); + double d = ceph::util::generate_random_number<double>(); + + swallow_values(def, l, i, d); + } + + // (optimistically) Check that the nullary and unary versions never return < 0: + { + for(long i = 0; 1000000 != i; i++) { + ASSERT_LE(0, ceph::util::generate_random_number()); + ASSERT_LE(0, ceph::util::generate_random_number(1)); + ASSERT_LE(0, ceph::util::generate_random_number<float>(1.0)); + } + } + + { + auto a = ceph::util::generate_random_number(1, std::numeric_limits<int>::max()); + auto b = ceph::util::generate_random_number(1, std::numeric_limits<int>::max()); + + if (std::numeric_limits<int>::max() > 32767) { + ASSERT_GT(a, 0); + ASSERT_GT(b, 0); + + ASSERT_NE(a, b); + } + } + + for (auto n = 100000; n; --n) { + int a = ceph::util::generate_random_number(0, 6); + ASSERT_GT(a, -1); + ASSERT_LT(a, 7); + } + + // Check bounding on zero (checking appropriate value for zero compiles and works): + for (auto n = 10; n; --n) { + ASSERT_EQ(0, ceph::util::generate_random_number<int>(0, 0)); + ASSERT_EQ(0, ceph::util::generate_random_number<float>(0.0, 0.0)); + } + + // Multiple types (integral): + { + int min = 0, max = 1; + type_check_ok(min, max); + } + + { + long min = 0, max = 1l; + type_check_ok(min, max); + } + + // Multiple types (floating point): + { + double min = 0.0, max = 1.0; + type_check_ok(min, max); + } + + { + float min = 0.0, max = 1.0; + type_check_ok(min, max); + } + + // When combining types, everything should convert to the largest type: + { + // Check with integral types: + { + int x = 0; + long long y = 1; + + auto z = ceph::util::generate_random_number(x, y); + + bool result = std::is_same_v<decltype(z), decltype(y)>; + + ASSERT_TRUE(result); + } + + // Check with floating-point types: + { + float x = 0.0; + long double y = 1.0; + + auto z = ceph::util::generate_random_number(x, y); + + bool result = std::is_same_v<decltype(z), decltype(y)>; + + ASSERT_TRUE(result); + } + + // It would be nice to have a test to check that mixing integral and floating point + // numbers should not compile, however we currently have no good way I know of + // to do such negative tests. + } +} + +TEST(util, test_random_class_interface) +{ + ceph::util::random_number_generator<int> rng_i; + ceph::util::random_number_generator<float> rng_f; + + // Other ctors: + { + ceph::util::random_number_generator<int> rng(1234); // seed + } + + // Test deduction guides: + { + { ceph::util::random_number_generator rng(1234); } +#pragma clang diagnostic push + // Turn this warning off, since we're checking that the deduction + // guide works. (And we don't know what the seed type will + // actually be.) +#pragma clang diagnostic ignored "-Wliteral-conversion" + { ceph::util::random_number_generator rng(1234.1234); } +#pragma clang diagnostic pop + + { + int x = 1234; + ceph::util::random_number_generator rng(x); + } + } + + { + int a = rng_i(); + int b = rng_i(); + + // Technically can fail, but should "almost never" happen: + ASSERT_NE(a, b); + } + + { + int a = rng_i(10); + ASSERT_LE(a, 10); + ASSERT_GE(a, 0); + } + + { + float a = rng_f(10.0); + ASSERT_LE(a, 10.0); + ASSERT_GE(a, 0.0); + } + + { + int a = rng_i(10, 20); + ASSERT_LE(a, 20); + ASSERT_GE(a, 10); + } + + { + float a = rng_f(10.0, 20.0); + ASSERT_LE(a, 20.0); + ASSERT_GE(a, 10.0); + } +} + diff --git a/src/test/common/test_safe_io.cc b/src/test/common/test_safe_io.cc new file mode 100644 index 000000000..8b98a9655 --- /dev/null +++ b/src/test/common/test_safe_io.cc @@ -0,0 +1,37 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include <algorithm> +#include <cstring> +#include <fcntl.h> +#include <unistd.h> + +#include "common/safe_io.h" + +#include "gtest/gtest.h" + + +TEST(SafeIO, safe_read_file) { + const char *fname = "safe_read_testfile"; + ::unlink(fname); + int fd = ::open(fname, O_RDWR|O_CREAT|O_TRUNC, 0600); + ASSERT_NE(fd, -1); + const char buf[] = "0123456789"; + for (int i = 0; i < 8; i++) { + ASSERT_EQ((ssize_t)sizeof(buf), write(fd, buf, sizeof(buf))); + } + ::close(fd); + char rdata[80]; + ASSERT_EQ((int)sizeof(rdata), + safe_read_file(".", fname, rdata, sizeof(rdata))); + for (char *p = rdata, *end = rdata+sizeof(rdata); p < end; p+=sizeof(buf)) { + ASSERT_EQ(0, std::memcmp(p, buf, std::min(size_t(end-p), sizeof(buf)))); + } + ::unlink(fname); +} + +// Local Variables: +// compile-command: "cd ../.. ; +// make unittest_safe_io && +// ./unittest_safe_io" +// End: diff --git a/src/test/common/test_shared_cache.cc b/src/test/common/test_shared_cache.cc new file mode 100644 index 000000000..91120c7e5 --- /dev/null +++ b/src/test/common/test_shared_cache.cc @@ -0,0 +1,400 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com> + * + * Author: Loic Dachary <loic@dachary.org> + * Cheng Cheng <ccheng.leo@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library Public License for more details. + * + */ + +#include <stdio.h> +#include <signal.h> +#include "gtest/gtest.h" +#include "common/Thread.h" +#include "common/shared_cache.hpp" + +using namespace std; + +class SharedLRUTest : public SharedLRU<unsigned int, int> { +public: + auto& get_lock() { return lock; } + auto& get_cond() { return cond; } + map<unsigned int, pair< std::weak_ptr<int>, int* > > &get_weak_refs() { + return weak_refs; + } +}; + +class SharedLRU_all : public ::testing::Test { +public: + + class Thread_wait : public Thread { + public: + SharedLRUTest &cache; + unsigned int key; + int value; + std::shared_ptr<int> ptr; + enum in_method_t { LOOKUP, LOWER_BOUND } in_method; + + Thread_wait(SharedLRUTest& _cache, unsigned int _key, + int _value, in_method_t _in_method) : + cache(_cache), + key(_key), + value(_value), + in_method(_in_method) { } + + void * entry() override { + switch (in_method) { + case LOWER_BOUND: + ptr = cache.lower_bound(key); + break; + case LOOKUP: + ptr = std::shared_ptr<int>(new int); + *ptr = value; + ptr = cache.lookup(key); + break; + } + return NULL; + } + }; + + static const useconds_t DELAY_MAX = 20 * 1000 * 1000; + static useconds_t delay; + + bool wait_for(SharedLRUTest &cache, int waitting) { + do { + // + // the delay variable is supposed to be initialized to zero. It would be fine + // to usleep(0) but we take this opportunity to test the loop. It will try + // again and therefore show that the logic ( increasing the delay ) actually + // works. + // + if (delay > 0) + usleep(delay); + { + std::lock_guard l{cache.get_lock()}; + if (cache.waiting == waitting) { + break; + } + } + if (delay > 0) { + cout << "delay " << delay << "us, is not long enough, try again\n"; + } + } while ((delay = delay * 2 + 1) < DELAY_MAX); + return delay < DELAY_MAX; + } +}; + +useconds_t SharedLRU_all::delay = 0; + +TEST_F(SharedLRU_all, add) { + SharedLRUTest cache; + unsigned int key = 1; + int value1 = 2; + bool existed = false; + { + std::shared_ptr<int> ptr = cache.add(key, new int(value1), &existed); + ASSERT_EQ(value1, *ptr); + ASSERT_FALSE(existed); + } + { + int value2 = 3; + auto p = new int(value2); + std::shared_ptr<int> ptr = cache.add(key, p, &existed); + ASSERT_EQ(value1, *ptr); + ASSERT_TRUE(existed); + delete p; + } +} +TEST_F(SharedLRU_all, empty) { + SharedLRUTest cache; + unsigned int key = 1; + bool existed = false; + + ASSERT_TRUE(cache.empty()); + { + int value1 = 2; + std::shared_ptr<int> ptr = cache.add(key, new int(value1), &existed); + ASSERT_EQ(value1, *ptr); + ASSERT_FALSE(existed); + } + ASSERT_FALSE(cache.empty()); + + cache.clear(key); + ASSERT_TRUE(cache.empty()); +} + +TEST_F(SharedLRU_all, lookup) { + SharedLRUTest cache; + unsigned int key = 1; + { + int value = 2; + ASSERT_TRUE(cache.add(key, new int(value)).get()); + ASSERT_TRUE(cache.lookup(key).get()); + ASSERT_EQ(value, *cache.lookup(key)); + } + ASSERT_TRUE(cache.lookup(key).get()); +} +TEST_F(SharedLRU_all, lookup_or_create) { + SharedLRUTest cache; + { + int value = 2; + unsigned int key = 1; + ASSERT_TRUE(cache.add(key, new int(value)).get()); + ASSERT_TRUE(cache.lookup_or_create(key).get()); + ASSERT_EQ(value, *cache.lookup(key)); + } + { + unsigned int key = 2; + ASSERT_TRUE(cache.lookup_or_create(key).get()); + ASSERT_EQ(0, *cache.lookup(key)); + } + ASSERT_TRUE(cache.lookup(1).get()); + ASSERT_TRUE(cache.lookup(2).get()); +} + +TEST_F(SharedLRU_all, wait_lookup) { + SharedLRUTest cache; + unsigned int key = 1; + int value = 2; + + { + std::shared_ptr<int> ptr(new int); + cache.get_weak_refs()[key] = make_pair(ptr, &*ptr); + } + EXPECT_FALSE(cache.get_weak_refs()[key].first.lock()); + + Thread_wait t(cache, key, value, Thread_wait::LOOKUP); + t.create("wait_lookup_1"); + ASSERT_TRUE(wait_for(cache, 1)); + EXPECT_EQ(value, *t.ptr); + // waiting on a key does not block lookups on other keys + EXPECT_FALSE(cache.lookup(key + 12345)); + { + std::lock_guard l{cache.get_lock()}; + cache.get_weak_refs().erase(key); + cache.get_cond().notify_one(); + } + ASSERT_TRUE(wait_for(cache, 0)); + t.join(); + EXPECT_FALSE(t.ptr); +} +TEST_F(SharedLRU_all, wait_lookup_or_create) { + SharedLRUTest cache; + unsigned int key = 1; + int value = 2; + + { + std::shared_ptr<int> ptr(new int); + cache.get_weak_refs()[key] = make_pair(ptr, &*ptr); + } + EXPECT_FALSE(cache.get_weak_refs()[key].first.lock()); + + Thread_wait t(cache, key, value, Thread_wait::LOOKUP); + t.create("wait_lookup_2"); + ASSERT_TRUE(wait_for(cache, 1)); + EXPECT_EQ(value, *t.ptr); + // waiting on a key does not block lookups on other keys + EXPECT_TRUE(cache.lookup_or_create(key + 12345).get()); + { + std::lock_guard l{cache.get_lock()}; + cache.get_weak_refs().erase(key); + cache.get_cond().notify_one(); + } + ASSERT_TRUE(wait_for(cache, 0)); + t.join(); + EXPECT_FALSE(t.ptr); +} + +TEST_F(SharedLRU_all, lower_bound) { + SharedLRUTest cache; + + { + unsigned int key = 1; + ASSERT_FALSE(cache.lower_bound(key)); + int value = 2; + + ASSERT_TRUE(cache.add(key, new int(value)).get()); + ASSERT_TRUE(cache.lower_bound(key).get()); + EXPECT_EQ(value, *cache.lower_bound(key)); + } +} + +TEST_F(SharedLRU_all, wait_lower_bound) { + SharedLRUTest cache; + unsigned int key = 1; + int value = 2; + unsigned int other_key = key + 1; + int other_value = value + 1; + + ASSERT_TRUE(cache.add(other_key, new int(other_value)).get()); + + { + std::shared_ptr<int> ptr(new int); + cache.get_weak_refs()[key] = make_pair(ptr, &*ptr); + } + EXPECT_FALSE(cache.get_weak_refs()[key].first.lock()); + + Thread_wait t(cache, key, value, Thread_wait::LOWER_BOUND); + t.create("wait_lower_bnd"); + ASSERT_TRUE(wait_for(cache, 1)); + EXPECT_FALSE(t.ptr); + // waiting on a key does not block getting lower_bound on other keys + EXPECT_TRUE(cache.lower_bound(other_key).get()); + { + std::lock_guard l{cache.get_lock()}; + cache.get_weak_refs().erase(key); + cache.get_cond().notify_one(); + } + ASSERT_TRUE(wait_for(cache, 0)); + t.join(); + EXPECT_TRUE(t.ptr.get()); +} +TEST_F(SharedLRU_all, get_next) { + + { + SharedLRUTest cache; + const unsigned int key = 0; + pair<unsigned int, int> i; + EXPECT_FALSE(cache.get_next(key, &i)); + } + { + SharedLRUTest cache; + + const unsigned int key2 = 333; + std::shared_ptr<int> ptr2 = cache.lookup_or_create(key2); + const int value2 = *ptr2 = 400; + + // entries with expired pointers are silently ignored + const unsigned int key_gone = 222; + cache.get_weak_refs()[key_gone] = make_pair(std::shared_ptr<int>(), (int*)0); + + const unsigned int key1 = 111; + std::shared_ptr<int> ptr1 = cache.lookup_or_create(key1); + const int value1 = *ptr1 = 800; + + pair<unsigned int, int> i; + EXPECT_TRUE(cache.get_next(0, &i)); + EXPECT_EQ(key1, i.first); + EXPECT_EQ(value1, i.second); + + EXPECT_TRUE(cache.get_next(i.first, &i)); + EXPECT_EQ(key2, i.first); + EXPECT_EQ(value2, i.second); + + EXPECT_FALSE(cache.get_next(i.first, &i)); + + cache.get_weak_refs().clear(); + } + { + SharedLRUTest cache; + const unsigned int key1 = 111; + std::shared_ptr<int> *ptr1 = new shared_ptr<int>(cache.lookup_or_create(key1)); + const unsigned int key2 = 222; + std::shared_ptr<int> ptr2 = cache.lookup_or_create(key2); + + pair<unsigned int, std::shared_ptr<int> > i; + EXPECT_TRUE(cache.get_next(i.first, &i)); + EXPECT_EQ(key1, i.first); + delete ptr1; + EXPECT_TRUE(cache.get_next(i.first, &i)); + EXPECT_EQ(key2, i.first); + } +} + +TEST_F(SharedLRU_all, clear) { + SharedLRUTest cache; + unsigned int key = 1; + int value = 2; + { + std::shared_ptr<int> ptr = cache.add(key, new int(value)); + ASSERT_EQ(value, *cache.lookup(key)); + } + ASSERT_TRUE(cache.lookup(key).get()); + cache.clear(key); + ASSERT_FALSE(cache.lookup(key)); + + { + std::shared_ptr<int> ptr = cache.add(key, new int(value)); + } + ASSERT_TRUE(cache.lookup(key).get()); + cache.clear(key); + ASSERT_FALSE(cache.lookup(key)); +} +TEST_F(SharedLRU_all, clear_all) { + SharedLRUTest cache; + unsigned int key = 1; + int value = 2; + { + std::shared_ptr<int> ptr = cache.add(key, new int(value)); + ASSERT_EQ(value, *cache.lookup(key)); + } + ASSERT_TRUE(cache.lookup(key).get()); + cache.clear(); + ASSERT_FALSE(cache.lookup(key)); + + std::shared_ptr<int> ptr2 = cache.add(key, new int(value)); + ASSERT_TRUE(cache.lookup(key).get()); + cache.clear(); + ASSERT_TRUE(cache.lookup(key).get()); + ASSERT_FALSE(cache.empty()); +} + +TEST(SharedCache_all, add) { + SharedLRU<int, int> cache; + unsigned int key = 1; + int value = 2; + std::shared_ptr<int> ptr = cache.add(key, new int(value)); + ASSERT_EQ(ptr, cache.lookup(key)); + ASSERT_EQ(value, *cache.lookup(key)); +} + +TEST(SharedCache_all, lru) { + const size_t SIZE = 5; + SharedLRU<int, int> cache(NULL, SIZE); + + bool existed = false; + std::shared_ptr<int> ptr = cache.add(0, new int(0), &existed); + ASSERT_FALSE(existed); + { + int *tmpint = new int(0); + std::shared_ptr<int> ptr2 = cache.add(0, tmpint, &existed); + ASSERT_TRUE(existed); + delete tmpint; + } + for (size_t i = 1; i < 2*SIZE; ++i) { + cache.add(i, new int(i), &existed); + ASSERT_FALSE(existed); + } + + ASSERT_TRUE(cache.lookup(0).get()); + ASSERT_EQ(0, *cache.lookup(0)); + + ASSERT_FALSE(cache.lookup(SIZE-1)); + ASSERT_FALSE(cache.lookup(SIZE)); + ASSERT_TRUE(cache.lookup(SIZE+1).get()); + ASSERT_EQ((int)SIZE+1, *cache.lookup(SIZE+1)); + + cache.purge(0); + ASSERT_FALSE(cache.lookup(0)); + std::shared_ptr<int> ptr2 = cache.add(0, new int(0), &existed); + ASSERT_FALSE(ptr == ptr2); + ptr = std::shared_ptr<int>(); + ASSERT_TRUE(cache.lookup(0).get()); +} + +// Local Variables: +// compile-command: "cd ../.. ; make unittest_shared_cache && ./unittest_shared_cache # --gtest_filter=*.* --log-to-stderr=true" +// End: diff --git a/src/test/common/test_sharedptr_registry.cc b/src/test/common/test_sharedptr_registry.cc new file mode 100644 index 000000000..9a856652e --- /dev/null +++ b/src/test/common/test_sharedptr_registry.cc @@ -0,0 +1,330 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com> + * + * Author: Loic Dachary <loic@dachary.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library Public License for more details. + * + */ + +#include <stdio.h> +#include <signal.h> +#include "gtest/gtest.h" +#include "common/Thread.h" +#include "common/sharedptr_registry.hpp" +#include "common/ceph_argparse.h" + +using namespace std; + +class SharedPtrRegistryTest : public SharedPtrRegistry<unsigned int, int> { +public: + ceph::mutex &get_lock() { return lock; } + map<unsigned int, pair<std::weak_ptr<int>, int*> > &get_contents() { + return contents; + } +}; + +class SharedPtrRegistry_all : public ::testing::Test { +public: + + class Thread_wait : public Thread { + public: + SharedPtrRegistryTest ®istry; + unsigned int key; + int value; + std::shared_ptr<int> ptr; + enum in_method_t { LOOKUP, LOOKUP_OR_CREATE } in_method; + + Thread_wait(SharedPtrRegistryTest& _registry, unsigned int _key, int _value, in_method_t _in_method) : + registry(_registry), + key(_key), + value(_value), + in_method(_in_method) + { + } + + void *entry() override { + switch(in_method) { + case LOOKUP_OR_CREATE: + if (value) + ptr = registry.lookup_or_create<int>(key, value); + else + ptr = registry.lookup_or_create(key); + break; + case LOOKUP: + ptr = std::shared_ptr<int>(new int); + *ptr = value; + ptr = registry.lookup(key); + break; + } + return NULL; + } + }; + + static const useconds_t DELAY_MAX = 20 * 1000 * 1000; + static useconds_t delay; + + bool wait_for(SharedPtrRegistryTest ®istry, int waiting) { + do { + // + // the delay variable is supposed to be initialized to zero. It would be fine + // to usleep(0) but we take this opportunity to test the loop. It will try + // again and therefore show that the logic ( increasing the delay ) actually + // works. + // + if (delay > 0) + usleep(delay); + { + std::lock_guard l(registry.get_lock()); + if (registry.waiting == waiting) + break; + } + if (delay > 0) + cout << "delay " << delay << "us, is not long enough, try again\n"; + } while (( delay = delay * 2 + 1) < DELAY_MAX); + return delay < DELAY_MAX; + } +}; + +useconds_t SharedPtrRegistry_all::delay = 0; + +TEST_F(SharedPtrRegistry_all, lookup_or_create) { + SharedPtrRegistryTest registry; + unsigned int key = 1; + int value = 2; + std::shared_ptr<int> ptr = registry.lookup_or_create(key); + *ptr = value; + ASSERT_EQ(value, *registry.lookup_or_create(key)); +} + +TEST_F(SharedPtrRegistry_all, wait_lookup_or_create) { + SharedPtrRegistryTest registry; + + // + // simulate the following: The last reference to a shared_ptr goes + // out of scope and the shared_ptr object is about to be removed and + // marked as such. The weak_ptr stored in the registry will show + // that it has expired(). However, the SharedPtrRegistry::OnRemoval + // object has not yet been called and did not get a chance to + // acquire the lock. The lookup_or_create and lookup methods must + // detect that situation and wait until the weak_ptr is removed from + // the registry. + // + { + unsigned int key = 1; + { + std::shared_ptr<int> ptr(new int); + registry.get_contents()[key] = make_pair(ptr, ptr.get()); + } + EXPECT_FALSE(registry.get_contents()[key].first.lock()); + + Thread_wait t(registry, key, 0, Thread_wait::LOOKUP_OR_CREATE); + t.create("wait_lookcreate"); + ASSERT_TRUE(wait_for(registry, 1)); + EXPECT_FALSE(t.ptr); + // waiting on a key does not block lookups on other keys + EXPECT_TRUE(registry.lookup_or_create(key + 12345).get()); + registry.remove(key); + ASSERT_TRUE(wait_for(registry, 0)); + t.join(); + EXPECT_TRUE(t.ptr.get()); + } + { + unsigned int key = 2; + int value = 3; + { + std::shared_ptr<int> ptr(new int); + registry.get_contents()[key] = make_pair(ptr, ptr.get()); + } + EXPECT_FALSE(registry.get_contents()[key].first.lock()); + + Thread_wait t(registry, key, value, Thread_wait::LOOKUP_OR_CREATE); + t.create("wait_lookcreate"); + ASSERT_TRUE(wait_for(registry, 1)); + EXPECT_FALSE(t.ptr); + // waiting on a key does not block lookups on other keys + { + int other_value = value + 1; + unsigned int other_key = key + 1; + std::shared_ptr<int> ptr = registry.lookup_or_create<int>(other_key, other_value); + EXPECT_TRUE(ptr.get()); + EXPECT_EQ(other_value, *ptr); + } + registry.remove(key); + ASSERT_TRUE(wait_for(registry, 0)); + t.join(); + EXPECT_TRUE(t.ptr.get()); + EXPECT_EQ(value, *t.ptr); + } +} + +TEST_F(SharedPtrRegistry_all, lookup) { + SharedPtrRegistryTest registry; + unsigned int key = 1; + { + std::shared_ptr<int> ptr = registry.lookup_or_create(key); + int value = 2; + *ptr = value; + ASSERT_EQ(value, *registry.lookup(key)); + } + ASSERT_FALSE(registry.lookup(key)); +} + +TEST_F(SharedPtrRegistry_all, wait_lookup) { + SharedPtrRegistryTest registry; + + unsigned int key = 1; + int value = 2; + { + std::shared_ptr<int> ptr(new int); + registry.get_contents()[key] = make_pair(ptr, ptr.get()); + } + EXPECT_FALSE(registry.get_contents()[key].first.lock()); + + Thread_wait t(registry, key, value, Thread_wait::LOOKUP); + t.create("wait_lookup"); + ASSERT_TRUE(wait_for(registry, 1)); + EXPECT_EQ(value, *t.ptr); + // waiting on a key does not block lookups on other keys + EXPECT_FALSE(registry.lookup(key + 12345)); + registry.remove(key); + ASSERT_TRUE(wait_for(registry, 0)); + t.join(); + EXPECT_FALSE(t.ptr); +} + +TEST_F(SharedPtrRegistry_all, get_next) { + + { + SharedPtrRegistry<unsigned int,int> registry; + const unsigned int key = 0; + pair<unsigned int, int> i; + EXPECT_FALSE(registry.get_next(key, &i)); + } + { + SharedPtrRegistryTest registry; + + const unsigned int key2 = 333; + std::shared_ptr<int> ptr2 = registry.lookup_or_create(key2); + const int value2 = *ptr2 = 400; + + // entries with expired pointers are silentely ignored + const unsigned int key_gone = 222; + registry.get_contents()[key_gone] = make_pair(std::shared_ptr<int>(), (int*)0); + + const unsigned int key1 = 111; + std::shared_ptr<int> ptr1 = registry.lookup_or_create(key1); + const int value1 = *ptr1 = 800; + + pair<unsigned int, int> i; + EXPECT_TRUE(registry.get_next(i.first, &i)); + EXPECT_EQ(key1, i.first); + EXPECT_EQ(value1, i.second); + + EXPECT_TRUE(registry.get_next(i.first, &i)); + EXPECT_EQ(key2, i.first); + EXPECT_EQ(value2, i.second); + + EXPECT_FALSE(registry.get_next(i.first, &i)); + } + { + // + // http://tracker.ceph.com/issues/6117 + // reproduce the issue. + // + SharedPtrRegistryTest registry; + const unsigned int key1 = 111; + std::shared_ptr<int> *ptr1 = new std::shared_ptr<int>(registry.lookup_or_create(key1)); + const unsigned int key2 = 222; + std::shared_ptr<int> ptr2 = registry.lookup_or_create(key2); + + pair<unsigned int, std::shared_ptr<int> > i; + EXPECT_TRUE(registry.get_next(i.first, &i)); + EXPECT_EQ(key1, i.first); + delete ptr1; + EXPECT_TRUE(registry.get_next(i.first, &i)); + EXPECT_EQ(key2, i.first); + } +} + +TEST_F(SharedPtrRegistry_all, remove) { + { + SharedPtrRegistryTest registry; + const unsigned int key1 = 1; + std::shared_ptr<int> ptr1 = registry.lookup_or_create(key1); + *ptr1 = 400; + registry.remove(key1); + + std::shared_ptr<int> ptr2 = registry.lookup_or_create(key1); + *ptr2 = 500; + + ptr1 = std::shared_ptr<int>(); + std::shared_ptr<int> res = registry.lookup(key1); + ceph_assert(res); + ceph_assert(res == ptr2); + ceph_assert(*res == 500); + } + { + SharedPtrRegistryTest registry; + const unsigned int key1 = 1; + std::shared_ptr<int> ptr1 = registry.lookup_or_create(key1, 400); + registry.remove(key1); + + std::shared_ptr<int> ptr2 = registry.lookup_or_create(key1, 500); + + ptr1 = std::shared_ptr<int>(); + std::shared_ptr<int> res = registry.lookup(key1); + ceph_assert(res); + ceph_assert(res == ptr2); + ceph_assert(*res == 500); + } +} + +class SharedPtrRegistry_destructor : public ::testing::Test { +public: + + typedef enum { UNDEFINED, YES, NO } DieEnum; + static DieEnum died; + + struct TellDie { + TellDie() { died = NO; } + ~TellDie() { died = YES; } + + int value = 0; + }; + + void SetUp() override { + died = UNDEFINED; + } +}; + +SharedPtrRegistry_destructor::DieEnum SharedPtrRegistry_destructor::died = SharedPtrRegistry_destructor::UNDEFINED; + +TEST_F(SharedPtrRegistry_destructor, destructor) { + SharedPtrRegistry<int,TellDie> registry; + EXPECT_EQ(UNDEFINED, died); + int key = 101; + { + std::shared_ptr<TellDie> a = registry.lookup_or_create(key); + EXPECT_EQ(NO, died); + EXPECT_TRUE(a.get()); + } + EXPECT_EQ(YES, died); + EXPECT_FALSE(registry.lookup(key)); +} + +// Local Variables: +// compile-command: "cd ../.. ; make unittest_sharedptr_registry && ./unittest_sharedptr_registry # --gtest_filter=*.* --log-to-stderr=true" +// End: diff --git a/src/test/common/test_shunique_lock.cc b/src/test/common/test_shunique_lock.cc new file mode 100644 index 000000000..b7696fe6e --- /dev/null +++ b/src/test/common/test_shunique_lock.cc @@ -0,0 +1,575 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 &smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License version 2, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <future> +#include <mutex> +#include <shared_mutex> +#include <thread> + +#include "common/ceph_time.h" +#include "common/shunique_lock.h" + +#include "gtest/gtest.h" + +template<typename SharedMutex> +static bool test_try_lock(SharedMutex* sm) { + if (!sm->try_lock()) + return false; + sm->unlock(); + return true; +} + +template<typename SharedMutex> +static bool test_try_lock_shared(SharedMutex* sm) { + if (!sm->try_lock_shared()) + return false; + sm->unlock_shared(); + return true; +} + +template<typename SharedMutex, typename AcquireType> +static void check_conflicts(SharedMutex sm, AcquireType) { +} + +template<typename SharedMutex> +static void ensure_conflicts(SharedMutex& sm, ceph::acquire_unique_t) { + auto ttl = &test_try_lock<std::shared_timed_mutex>; + auto ttls = &test_try_lock_shared<std::shared_timed_mutex>; + ASSERT_FALSE(std::async(std::launch::async, ttl, &sm).get()); + ASSERT_FALSE(std::async(std::launch::async, ttls, &sm).get()); +} + +template<typename SharedMutex> +static void ensure_conflicts(SharedMutex& sm, ceph::acquire_shared_t) { + auto ttl = &test_try_lock<std::shared_timed_mutex>; + auto ttls = &test_try_lock_shared<std::shared_timed_mutex>; + ASSERT_FALSE(std::async(std::launch::async, ttl, &sm).get()); + ASSERT_TRUE(std::async(std::launch::async, ttls, &sm).get()); +} + +template<typename SharedMutex> +static void ensure_free(SharedMutex& sm) { + auto ttl = &test_try_lock<std::shared_timed_mutex>; + auto ttls = &test_try_lock_shared<std::shared_timed_mutex>; + ASSERT_TRUE(std::async(std::launch::async, ttl, &sm).get()); + ASSERT_TRUE(std::async(std::launch::async, ttls, &sm).get()); +} + +template<typename SharedMutex, typename AcquireType> +static void check_owns_lock(const SharedMutex& sm, + const ceph::shunique_lock<SharedMutex>& sul, + AcquireType) { +} + +template<typename SharedMutex> +static void check_owns_lock(const SharedMutex& sm, + const ceph::shunique_lock<SharedMutex>& sul, + ceph::acquire_unique_t) { + ASSERT_TRUE(sul.mutex() == &sm); + ASSERT_TRUE(sul.owns_lock()); + ASSERT_TRUE(!!sul); +} + +template<typename SharedMutex> +static void check_owns_lock(const SharedMutex& sm, + const ceph::shunique_lock<SharedMutex>& sul, + ceph::acquire_shared_t) { + ASSERT_TRUE(sul.owns_lock_shared()); + ASSERT_TRUE(!!sul); +} + +template<typename SharedMutex> +static void check_abjures_lock(const SharedMutex& sm, + const ceph::shunique_lock<SharedMutex>& sul) { + ASSERT_EQ(sul.mutex(), &sm); + ASSERT_FALSE(sul.owns_lock()); + ASSERT_FALSE(sul.owns_lock_shared()); + ASSERT_FALSE(!!sul); +} + +template<typename SharedMutex> +static void check_abjures_lock(const ceph::shunique_lock<SharedMutex>& sul) { + ASSERT_EQ(sul.mutex(), nullptr); + ASSERT_FALSE(sul.owns_lock()); + ASSERT_FALSE(sul.owns_lock_shared()); + ASSERT_FALSE(!!sul); +} + +TEST(ShuniqueLock, DefaultConstructor) { + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + shunique_lock l; + + ASSERT_EQ(l.mutex(), nullptr); + ASSERT_FALSE(l.owns_lock()); + ASSERT_FALSE(!!l); + + ASSERT_THROW(l.lock(), std::system_error); + ASSERT_THROW(l.try_lock(), std::system_error); + + ASSERT_THROW(l.lock_shared(), std::system_error); + ASSERT_THROW(l.try_lock_shared(), std::system_error); + + ASSERT_THROW(l.unlock(), std::system_error); + + ASSERT_EQ(l.mutex(), nullptr); + ASSERT_FALSE(l.owns_lock()); + ASSERT_FALSE(!!l); + + ASSERT_EQ(l.release(), nullptr); + + ASSERT_EQ(l.mutex(), nullptr); + ASSERT_FALSE(l.owns_lock()); + ASSERT_FALSE(l.owns_lock_shared()); + ASSERT_FALSE(!!l); +} + +template<typename AcquireType> +void lock_unlock(AcquireType at) { + std::shared_timed_mutex sm; + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + shunique_lock l(sm, at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + + l.unlock(); + + check_abjures_lock(sm, l); + ensure_free(sm); + + l.lock(at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); +} + +TEST(ShuniqueLock, LockUnlock) { + lock_unlock(ceph::acquire_unique); + lock_unlock(ceph::acquire_shared); +} + +template<typename AcquireType> +void lock_destruct(AcquireType at) { + std::shared_timed_mutex sm; + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + { + shunique_lock l(sm, at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + } + + ensure_free(sm); +} + +TEST(ShuniqueLock, LockDestruct) { + lock_destruct(ceph::acquire_unique); + lock_destruct(ceph::acquire_shared); +} + +template<typename AcquireType> +void move_construct(AcquireType at) { + std::shared_timed_mutex sm; + + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + { + shunique_lock l(sm, at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + + shunique_lock o(std::move(l)); + + check_abjures_lock(l); + + check_owns_lock(sm, o, at); + ensure_conflicts(sm, at); + + o.unlock(); + + shunique_lock c(std::move(o)); + + + ASSERT_EQ(o.mutex(), nullptr); + ASSERT_FALSE(!!o); + + check_abjures_lock(sm, c); + + ensure_free(sm); + } +} + +TEST(ShuniqueLock, MoveConstruct) { + move_construct(ceph::acquire_unique); + move_construct(ceph::acquire_shared); + + std::shared_timed_mutex sm; + { + std::unique_lock<std::shared_timed_mutex> ul(sm); + ensure_conflicts(sm, ceph::acquire_unique); + ceph::shunique_lock<std::shared_timed_mutex> l(std::move(ul)); + check_owns_lock(sm, l, ceph::acquire_unique); + ensure_conflicts(sm, ceph::acquire_unique); + } + { + std::unique_lock<std::shared_timed_mutex> ul(sm, std::defer_lock); + ensure_free(sm); + ceph::shunique_lock<std::shared_timed_mutex> l(std::move(ul)); + check_abjures_lock(sm, l); + ensure_free(sm); + } + { + std::unique_lock<std::shared_timed_mutex> ul; + ceph::shunique_lock<std::shared_timed_mutex> l(std::move(ul)); + check_abjures_lock(l); + } + { + std::shared_lock<std::shared_timed_mutex> sl(sm); + ensure_conflicts(sm, ceph::acquire_shared); + ceph::shunique_lock<std::shared_timed_mutex> l(std::move(sl)); + check_owns_lock(sm, l, ceph::acquire_shared); + ensure_conflicts(sm, ceph::acquire_shared); + } + { + std::shared_lock<std::shared_timed_mutex> sl; + ceph::shunique_lock<std::shared_timed_mutex> l(std::move(sl)); + check_abjures_lock(l); + } +} + +template<typename AcquireType> +void move_assign(AcquireType at) { + std::shared_timed_mutex sm; + + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + { + shunique_lock l(sm, at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + + shunique_lock o; + + o = std::move(l); + + check_abjures_lock(l); + + check_owns_lock(sm, o, at); + ensure_conflicts(sm, at); + + o.unlock(); + + shunique_lock c(std::move(o)); + + check_abjures_lock(o); + check_abjures_lock(sm, c); + + ensure_free(sm); + + shunique_lock k; + + c = std::move(k); + + check_abjures_lock(k); + check_abjures_lock(c); + + ensure_free(sm); + } +} + +TEST(ShuniqueLock, MoveAssign) { + move_assign(ceph::acquire_unique); + move_assign(ceph::acquire_shared); + + std::shared_timed_mutex sm; + { + std::unique_lock<std::shared_timed_mutex> ul(sm); + ensure_conflicts(sm, ceph::acquire_unique); + ceph::shunique_lock<std::shared_timed_mutex> l; + l = std::move(ul); + check_owns_lock(sm, l, ceph::acquire_unique); + ensure_conflicts(sm, ceph::acquire_unique); + } + { + std::unique_lock<std::shared_timed_mutex> ul(sm, std::defer_lock); + ensure_free(sm); + ceph::shunique_lock<std::shared_timed_mutex> l; + l = std::move(ul); + check_abjures_lock(sm, l); + ensure_free(sm); + } + { + std::unique_lock<std::shared_timed_mutex> ul; + ceph::shunique_lock<std::shared_timed_mutex> l; + l = std::move(ul); + check_abjures_lock(l); + } + { + std::shared_lock<std::shared_timed_mutex> sl(sm); + ensure_conflicts(sm, ceph::acquire_shared); + ceph::shunique_lock<std::shared_timed_mutex> l; + l = std::move(sl); + check_owns_lock(sm, l, ceph::acquire_shared); + ensure_conflicts(sm, ceph::acquire_shared); + } + { + std::shared_lock<std::shared_timed_mutex> sl; + ceph::shunique_lock<std::shared_timed_mutex> l; + l = std::move(sl); + check_abjures_lock(l); + } + +} + +template<typename AcquireType> +void construct_deferred(AcquireType at) { + std::shared_timed_mutex sm; + + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + { + shunique_lock l(sm, std::defer_lock); + check_abjures_lock(sm, l); + ensure_free(sm); + + ASSERT_THROW(l.unlock(), std::system_error); + + check_abjures_lock(sm, l); + ensure_free(sm); + + l.lock(at); + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + } + + { + shunique_lock l(sm, std::defer_lock); + check_abjures_lock(sm, l); + ensure_free(sm); + + ASSERT_THROW(l.unlock(), std::system_error); + + check_abjures_lock(sm, l); + ensure_free(sm); + } + ensure_free(sm); +} + +TEST(ShuniqueLock, ConstructDeferred) { + construct_deferred(ceph::acquire_unique); + construct_deferred(ceph::acquire_shared); +} + +template<typename AcquireType> +void construct_try(AcquireType at) { + std::shared_timed_mutex sm; + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + { + shunique_lock l(sm, at, std::try_to_lock); + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + } + + { + std::unique_lock<std::shared_timed_mutex> l(sm); + ensure_conflicts(sm, ceph::acquire_unique); + + std::async(std::launch::async, [&sm, at]() { + shunique_lock l(sm, at, std::try_to_lock); + check_abjures_lock(sm, l); + ensure_conflicts(sm, ceph::acquire_unique); + }).get(); + + l.unlock(); + + std::async(std::launch::async, [&sm, at]() { + shunique_lock l(sm, at, std::try_to_lock); + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + }).get(); + } +} + +TEST(ShuniqueLock, ConstructTry) { + construct_try(ceph::acquire_unique); + construct_try(ceph::acquire_shared); +} + +template<typename AcquireType> +void construct_adopt(AcquireType at) { + std::shared_timed_mutex sm; + + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + { + shunique_lock d(sm, at); + d.release(); + } + + ensure_conflicts(sm, at); + + { + shunique_lock l(sm, at, std::adopt_lock); + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + } + + ensure_free(sm); +} + +TEST(ShuniqueLock, ConstructAdopt) { + construct_adopt(ceph::acquire_unique); + construct_adopt(ceph::acquire_shared); +} + +template<typename AcquireType> +void try_lock(AcquireType at) { + std::shared_timed_mutex sm; + + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + { + shunique_lock l(sm, std::defer_lock); + l.try_lock(at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + } + + { + std::unique_lock<std::shared_timed_mutex> l(sm); + + std::async(std::launch::async, [&sm, at]() { + shunique_lock l(sm, std::defer_lock); + l.try_lock(at); + + check_abjures_lock(sm, l); + ensure_conflicts(sm, ceph::acquire_unique); + }).get(); + + + l.unlock(); + std::async(std::launch::async, [&sm, at]() { + shunique_lock l(sm, std::defer_lock); + l.try_lock(at); + + check_owns_lock(sm, l, at); + ensure_conflicts(sm, at); + }).get(); + } +} + +TEST(ShuniqueLock, TryLock) { + try_lock(ceph::acquire_unique); + try_lock(ceph::acquire_shared); +} + +TEST(ShuniqueLock, Release) { + std::shared_timed_mutex sm; + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + { + shunique_lock l(sm, ceph::acquire_unique); + check_owns_lock(sm, l, ceph::acquire_unique); + ensure_conflicts(sm, ceph::acquire_unique); + + l.release(); + check_abjures_lock(l); + ensure_conflicts(sm, ceph::acquire_unique); + } + ensure_conflicts(sm, ceph::acquire_unique); + sm.unlock(); + ensure_free(sm); + + { + shunique_lock l(sm, ceph::acquire_shared); + check_owns_lock(sm, l, ceph::acquire_shared); + ensure_conflicts(sm, ceph::acquire_shared); + + l.release(); + check_abjures_lock(l); + ensure_conflicts(sm, ceph::acquire_shared); + } + ensure_conflicts(sm, ceph::acquire_shared); + sm.unlock_shared(); + ensure_free(sm); + + sm.lock(); + { + shunique_lock l(sm, std::defer_lock); + check_abjures_lock(sm, l); + ensure_conflicts(sm, ceph::acquire_unique); + + l.release(); + check_abjures_lock(l); + ensure_conflicts(sm, ceph::acquire_unique); + } + ensure_conflicts(sm, ceph::acquire_unique); + sm.unlock(); + + ensure_free(sm); + + { + std::unique_lock<std::shared_timed_mutex> ul; + shunique_lock l(sm, std::defer_lock); + check_abjures_lock(sm, l); + ensure_free(sm); + + ASSERT_NO_THROW(ul = l.release_to_unique()); + check_abjures_lock(l); + ASSERT_EQ(ul.mutex(), &sm); + ASSERT_FALSE(ul.owns_lock()); + ensure_free(sm); + } + ensure_free(sm); + + { + std::unique_lock<std::shared_timed_mutex> ul; + shunique_lock l; + check_abjures_lock(l); + + ASSERT_NO_THROW(ul = l.release_to_unique()); + check_abjures_lock(l); + ASSERT_EQ(ul.mutex(), nullptr); + ASSERT_FALSE(ul.owns_lock()); + } +} + +TEST(ShuniqueLock, NoRecursion) { + std::shared_timed_mutex sm; + + typedef ceph::shunique_lock<std::shared_timed_mutex> shunique_lock; + + { + shunique_lock l(sm, ceph::acquire_unique); + ASSERT_THROW(l.lock(), std::system_error); + ASSERT_THROW(l.try_lock(), std::system_error); + ASSERT_THROW(l.lock_shared(), std::system_error); + ASSERT_THROW(l.try_lock_shared(), std::system_error); + } + + { + shunique_lock l(sm, ceph::acquire_shared); + ASSERT_THROW(l.lock(), std::system_error); + ASSERT_THROW(l.try_lock(), std::system_error); + ASSERT_THROW(l.lock_shared(), std::system_error); + ASSERT_THROW(l.try_lock_shared(), std::system_error); + } +} diff --git a/src/test/common/test_sloppy_crc_map.cc b/src/test/common/test_sloppy_crc_map.cc new file mode 100644 index 000000000..9d9130cb7 --- /dev/null +++ b/src/test/common/test_sloppy_crc_map.cc @@ -0,0 +1,116 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include <iostream> + +#include "common/SloppyCRCMap.h" +#include "common/Formatter.h" +#include <gtest/gtest.h> + +using namespace std; + +void dump(const SloppyCRCMap& scm) +{ + auto f = Formatter::create_unique("json-pretty"); + f->open_object_section("map"); + scm.dump(f.get()); + f->close_section(); + f->flush(cout); +} + +TEST(SloppyCRCMap, basic) { + SloppyCRCMap scm(4); + + bufferlist a, b; + a.append("The quick brown fox jumped over a fence whose color I forget."); + b.append("asdf"); + + scm.write(0, a.length(), a); + if (0) + dump(scm); + ASSERT_EQ(0, scm.read(0, a.length(), a, &cout)); + + scm.write(12, b.length(), b); + if (0) + dump(scm); + + ASSERT_EQ(0, scm.read(12, b.length(), b, &cout)); + ASSERT_EQ(1, scm.read(0, a.length(), a, &cout)); +} + +TEST(SloppyCRCMap, truncate) { + SloppyCRCMap scm(4); + + bufferlist a, b; + a.append("asdf"); + b.append("qwer"); + + scm.write(0, a.length(), a); + scm.write(4, a.length(), a); + ASSERT_EQ(0, scm.read(4, 4, a, &cout)); + ASSERT_EQ(1, scm.read(4, 4, b, &cout)); + scm.truncate(4); + ASSERT_EQ(0, scm.read(4, 4, b, &cout)); +} + +TEST(SloppyCRCMap, zero) { + SloppyCRCMap scm(4); + + bufferlist a, b; + a.append("asdf"); + b.append("qwer"); + + scm.write(0, a.length(), a); + scm.write(4, a.length(), a); + ASSERT_EQ(0, scm.read(4, 4, a, &cout)); + ASSERT_EQ(1, scm.read(4, 4, b, &cout)); + scm.zero(4, 4); + ASSERT_EQ(1, scm.read(4, 4, a, &cout)); + ASSERT_EQ(1, scm.read(4, 4, b, &cout)); + + bufferptr bp(4); + bp.zero(); + bufferlist c; + c.append(bp); + ASSERT_EQ(0, scm.read(0, 4, a, &cout)); + ASSERT_EQ(0, scm.read(4, 4, c, &cout)); + scm.zero(0, 15); + ASSERT_EQ(1, scm.read(0, 4, a, &cout)); + ASSERT_EQ(0, scm.read(0, 4, c, &cout)); +} + +TEST(SloppyCRCMap, clone_range) { + SloppyCRCMap src(4); + SloppyCRCMap dst(4); + + bufferlist a, b; + a.append("asdfghjkl"); + b.append("qwertyui"); + + src.write(0, a.length(), a); + src.write(8, a.length(), a); + src.write(16, a.length(), a); + + dst.write(0, b.length(), b); + dst.clone_range(0, 8, 0, src); + ASSERT_EQ(2, dst.read(0, 8, b, &cout)); + ASSERT_EQ(0, dst.read(8, 8, b, &cout)); + + dst.write(16, b.length(), b); + ASSERT_EQ(2, dst.read(16, 8, a, &cout)); + dst.clone_range(16, 8, 16, src); + ASSERT_EQ(0, dst.read(16, 8, a, &cout)); + + dst.write(16, b.length(), b); + ASSERT_EQ(1, dst.read(16, 4, a, &cout)); + dst.clone_range(16, 8, 2, src); + ASSERT_EQ(0, dst.read(16, 4, a, &cout)); + + dst.write(0, b.length(), b); + dst.write(8, b.length(), b); + ASSERT_EQ(2, dst.read(0, 8, a, &cout)); + ASSERT_EQ(2, dst.read(8, 8, a, &cout)); + dst.clone_range(2, 8, 0, src); + ASSERT_EQ(0, dst.read(0, 8, a, &cout)); + ASSERT_EQ(0, dst.read(8, 4, a, &cout)); +} diff --git a/src/test/common/test_split.cc b/src/test/common/test_split.cc new file mode 100644 index 000000000..285dea752 --- /dev/null +++ b/src/test/common/test_split.cc @@ -0,0 +1,119 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2019 Red Hat, Inc. + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "common/split.h" +#include <algorithm> +#include <gtest/gtest.h> + +namespace ceph { + +using string_list = std::initializer_list<std::string_view>; + +bool operator==(const split& lhs, const string_list& rhs) { + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} +bool operator==(const string_list& lhs, const split& rhs) { + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} + +TEST(split, split) +{ + EXPECT_EQ(string_list({}), split("")); + EXPECT_EQ(string_list({}), split(",")); + EXPECT_EQ(string_list({}), split(",;")); + + EXPECT_EQ(string_list({"a"}), split("a,;")); + EXPECT_EQ(string_list({"a"}), split(",a;")); + EXPECT_EQ(string_list({"a"}), split(",;a")); + + EXPECT_EQ(string_list({"a", "b"}), split("a,b;")); + EXPECT_EQ(string_list({"a", "b"}), split("a,;b")); + EXPECT_EQ(string_list({"a", "b"}), split(",a;b")); +} + +TEST(split, iterator_indirection) +{ + const auto parts = split("a,b"); + auto i = parts.begin(); + ASSERT_NE(i, parts.end()); + EXPECT_EQ("a", *i); // test operator* +} + +TEST(split, iterator_dereference) +{ + const auto parts = split("a,b"); + auto i = parts.begin(); + ASSERT_NE(i, parts.end()); + EXPECT_EQ(1, i->size()); // test operator-> +} + +TEST(split, iterator_pre_increment) +{ + const auto parts = split("a,b"); + auto i = parts.begin(); + ASSERT_NE(i, parts.end()); + + ASSERT_EQ("a", *i); + EXPECT_EQ("b", *++i); // test operator++() + EXPECT_EQ("b", *i); +} + +TEST(split, iterator_post_increment) +{ + const auto parts = split("a,b"); + auto i = parts.begin(); + ASSERT_NE(i, parts.end()); + + ASSERT_EQ("a", *i); + EXPECT_EQ("a", *i++); // test operator++(int) + ASSERT_NE(parts.end(), i); + EXPECT_EQ("b", *i); +} + +TEST(split, iterator_singular) +{ + const auto parts = split("a,b"); + auto i = parts.begin(); + + // test comparions against default-constructed 'singular' iterators + split::iterator j; + split::iterator k; + EXPECT_EQ(j, parts.end()); // singular == end + EXPECT_EQ(j, k); // singular == singular + EXPECT_NE(j, i); // singular != valid +} + +TEST(split, iterator_multipass) +{ + const auto parts = split("a,b"); + auto i = parts.begin(); + ASSERT_NE(i, parts.end()); + + // copy the iterator to test LegacyForwardIterator's multipass guarantee + auto j = i; + ASSERT_EQ(i, j); + + ASSERT_EQ("a", *i); + ASSERT_NE(parts.end(), ++i); + EXPECT_EQ("b", *i); + + ASSERT_EQ("a", *j); // test that ++i left j unmodified + ASSERT_NE(parts.end(), ++j); + EXPECT_EQ("b", *j); + + EXPECT_EQ(i, j); +} + +} // namespace ceph diff --git a/src/test/common/test_static_ptr.cc b/src/test/common/test_static_ptr.cc new file mode 100644 index 000000000..0a4073e75 --- /dev/null +++ b/src/test/common/test_static_ptr.cc @@ -0,0 +1,216 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2017 Red Hat, Inc. + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <compare> +#include <gtest/gtest.h> +#include "common/static_ptr.h" + +using ceph::static_ptr; +using ceph::make_static; + +class base { +public: + virtual int func() = 0; + virtual ~base() = default; +}; + +class sibling1 : public base { +public: + int func() override { return 0; } +}; + +class sibling2 : public base { +public: + int func() override { return 9; } + virtual int call(int) = 0; +}; + +class grandchild : public sibling2 { +protected: + int val; +public: + explicit grandchild(int val) : val(val) {} + virtual int call(int n) override { return n * val; } +}; + +class great_grandchild : public grandchild { +public: + explicit great_grandchild(int val) : grandchild(val) {} + int call(int n) override { return n + val; } +}; + +#ifdef __cpp_lib_three_way_comparison +TEST(StaticPtr, EmptyCreation) { + static_ptr<base, sizeof(grandchild)> p; + EXPECT_FALSE(p); + EXPECT_EQ(p, nullptr); + EXPECT_EQ(nullptr, p); + EXPECT_TRUE(p.get() == nullptr); +} + +TEST(StaticPtr, CreationCall) { + { + static_ptr<base, sizeof(grandchild)> p(std::in_place_type_t<sibling1>{}); + EXPECT_TRUE(p); + EXPECT_FALSE(p == nullptr); + EXPECT_FALSE(nullptr == p); + EXPECT_FALSE(p.get() == nullptr); + EXPECT_EQ(p->func(), 0); + EXPECT_EQ((*p).func(), 0); + EXPECT_EQ((p.get())->func(), 0); + } + { + auto p = make_static<base, sibling1>(); + EXPECT_TRUE(p); + EXPECT_FALSE(p == nullptr); + EXPECT_FALSE(nullptr == p); + EXPECT_FALSE(p.get() == nullptr); + EXPECT_EQ(p->func(), 0); + EXPECT_EQ((*p).func(), 0); + EXPECT_EQ((p.get())->func(), 0); + } +} + +TEST(StaticPtr, CreateReset) { + { + static_ptr<base, sizeof(grandchild)> p(std::in_place_type_t<sibling1>{}); + EXPECT_EQ((p.get())->func(), 0); + p.reset(); + EXPECT_FALSE(p); + EXPECT_EQ(p, nullptr); + EXPECT_EQ(nullptr, p); + EXPECT_TRUE(p.get() == nullptr); + } + { + static_ptr<base, sizeof(grandchild)> p(std::in_place_type_t<sibling1>{}); + EXPECT_EQ((p.get())->func(), 0); + p = nullptr; + EXPECT_FALSE(p); + EXPECT_EQ(p, nullptr); + EXPECT_EQ(nullptr, p); + EXPECT_TRUE(p.get() == nullptr); + } +} +#endif // __cpp_lib_three_way_comparison + +TEST(StaticPtr, CreateEmplace) { + static_ptr<base, sizeof(grandchild)> p(std::in_place_type_t<sibling1>{}); + EXPECT_EQ((p.get())->func(), 0); + p.emplace<grandchild>(30); + EXPECT_EQ(p->func(), 9); +} + +TEST(StaticPtr, Move) { + // Won't compile. Good. + // static_ptr<base, sizeof(base)> p1(std::in_place_type_t<grandchild>{}, 3); + + static_ptr<base, sizeof(base)> p1(std::in_place_type_t<sibling1>{}); + static_ptr<base, sizeof(grandchild)> p2(std::in_place_type_t<grandchild>{}, + 3); + + p2 = std::move(p1); + EXPECT_EQ(p1->func(), 0); +} + +TEST(StaticPtr, ImplicitUpcast) { + static_ptr<base, sizeof(grandchild)> p1; + static_ptr<sibling2, sizeof(grandchild)> p2(std::in_place_type_t<grandchild>{}, 3); + + p1 = std::move(p2); + EXPECT_EQ(p1->func(), 9); + + p2.reset(); + + // Doesn't compile. Good. + // p2 = p1; +} + +TEST(StaticPtr, StaticCast) { + static_ptr<base, sizeof(grandchild)> p1(std::in_place_type_t<grandchild>{}, 3); + static_ptr<sibling2, sizeof(grandchild)> p2; + + p2 = ceph::static_pointer_cast<sibling2, sizeof(grandchild)>(std::move(p1)); + EXPECT_EQ(p2->func(), 9); + EXPECT_EQ(p2->call(10), 30); +} + +TEST(StaticPtr, DynamicCast) { + static constexpr auto sz = sizeof(great_grandchild); + { + static_ptr<base, sz> p1(std::in_place_type_t<grandchild>{}, 3); + auto p2 = ceph::dynamic_pointer_cast<great_grandchild, sz>(std::move(p1)); + EXPECT_FALSE(p2); + } + + { + static_ptr<base, sz> p1(std::in_place_type_t<grandchild>{}, 3); + auto p2 = ceph::dynamic_pointer_cast<grandchild, sz>(std::move(p1)); + EXPECT_TRUE(p2); + EXPECT_EQ(p2->func(), 9); + EXPECT_EQ(p2->call(10), 30); + } +} + +class constable { +public: + int foo() { + return 2; + } + int foo() const { + return 5; + } +}; + +TEST(StaticPtr, ConstCast) { + static constexpr auto sz = sizeof(constable); + { + auto p1 = make_static<const constable>(); + EXPECT_EQ(p1->foo(), 5); + auto p2 = ceph::const_pointer_cast<constable, sz>(std::move(p1)); + static_assert(!std::is_const<decltype(p2)::element_type>{}, + "Things are more const than they ought to be."); + EXPECT_TRUE(p2); + EXPECT_EQ(p2->foo(), 2); + } +} + +TEST(StaticPtr, ReinterpretCast) { + static constexpr auto sz = sizeof(grandchild); + { + auto p1 = make_static<grandchild>(3); + auto p2 = ceph::reinterpret_pointer_cast<constable, sz>(std::move(p1)); + static_assert(std::is_same<decltype(p2)::element_type, constable>{}, + "Reinterpret is screwy."); + auto p3 = ceph::reinterpret_pointer_cast<grandchild, sz>(std::move(p2)); + static_assert(std::is_same<decltype(p3)::element_type, grandchild>{}, + "Reinterpret is screwy."); + EXPECT_EQ(p3->func(), 9); + EXPECT_EQ(p3->call(10), 30); + } +} + +struct exceptional { + exceptional() = default; + exceptional(const exceptional& e) { + throw std::exception(); + } + exceptional(exceptional&& e) { + throw std::exception(); + } +}; + +TEST(StaticPtr, Exceptional) { + static_ptr<exceptional> p1(std::in_place_type_t<exceptional>{}); + EXPECT_ANY_THROW(static_ptr<exceptional> p2(std::move(p1))); +} diff --git a/src/test/common/test_str_map.cc b/src/test/common/test_str_map.cc new file mode 100644 index 000000000..b61739e8f --- /dev/null +++ b/src/test/common/test_str_map.cc @@ -0,0 +1,89 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com> + * + * Author: Loic Dachary <loic@dachary.org> + * + * 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 <errno.h> +#include <gtest/gtest.h> + +#include "include/str_map.h" + +using namespace std; + +TEST(str_map, json) { + map<string,string> str_map; + stringstream ss; + // well formatted + ASSERT_EQ(0, get_json_str_map("{\"key\": \"value\"}", ss, &str_map)); + ASSERT_EQ("value", str_map["key"]); + // well formatted but not a JSON object + ASSERT_EQ(-EINVAL, get_json_str_map("\"key\"", ss, &str_map)); + ASSERT_NE(string::npos, ss.str().find("must be a JSON object")); +} + +TEST(str_map, plaintext) { + { + map<string,string> str_map; + ASSERT_EQ(0, get_str_map(" foo=bar\t\nfrob=nitz yeah right= \n\t", + &str_map)); + ASSERT_EQ(4u, str_map.size()); + ASSERT_EQ("bar", str_map["foo"]); + ASSERT_EQ("nitz", str_map["frob"]); + ASSERT_EQ("", str_map["yeah"]); + ASSERT_EQ("", str_map["right"]); + } + { + map<string,string> str_map; + ASSERT_EQ(0, get_str_map("that", &str_map)); + ASSERT_EQ(1u, str_map.size()); + ASSERT_EQ("", str_map["that"]); + } + { + map<string,string> str_map; + ASSERT_EQ(0, get_str_map(" \t \n ", &str_map)); + ASSERT_EQ(0u, str_map.size()); + ASSERT_EQ(0, get_str_map("", &str_map)); + ASSERT_EQ(0u, str_map.size()); + } + { + map<string,string> str_map; + ASSERT_EQ(0, get_str_map(" key1=val1; key2=\tval2; key3\t = \t val3; \n ", &str_map, "\n;")); + ASSERT_EQ(4u, str_map.size()); + ASSERT_EQ("val1", str_map["key1"]); + ASSERT_EQ("val2", str_map["key2"]); + ASSERT_EQ("val3", str_map["key3"]); + } +} + +TEST(str_map, empty_values) { + { + map<string,string> str_map; + ASSERT_EQ(0, get_str_map("M= P= L=", + &str_map)); + ASSERT_EQ(3u, str_map.size()); + ASSERT_EQ("", str_map["M"]); + ASSERT_EQ("", str_map["P"]); + ASSERT_EQ("", str_map["L"]); + } +} + +/* + * Local Variables: + * compile-command: "cd ../.. ; make -j4 && + * make unittest_str_map && + * valgrind --tool=memcheck --leak-check=full \ + * ./unittest_str_map + * " + * End: + */ diff --git a/src/test/common/test_tableformatter.cc b/src/test/common/test_tableformatter.cc new file mode 100644 index 000000000..b152014a2 --- /dev/null +++ b/src/test/common/test_tableformatter.cc @@ -0,0 +1,263 @@ +#include "gtest/gtest.h" + +#include "common/Formatter.h" +#include <iostream> +#include <sstream> +#include <string> + +using namespace ceph; + +TEST(tableformatter, singleline) +{ + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer", 10); + formatter.dump_float("float", 10.0); + formatter.dump_string("string", "string"); + formatter.flush(sout); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n"; + EXPECT_EQ(cmp, sout.str()); +} + +TEST(tableformatter, longfloat) +{ + std::stringstream sout; + TableFormatter formatter; + formatter.dump_float("float", 1.0 / 7); + formatter.flush(sout); + + std::string cmp = "" + "+----------------------+\n" + "| float |\n" + "+----------------------+\n" + "| 0.14285714285714285 |\n" + "+----------------------+\n"; + EXPECT_EQ(cmp, sout.str()); +} + +TEST(tableformatter, multiline) +{ + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer", 10); + formatter.dump_float("float", 10.0); + formatter.dump_string("string", "string"); + formatter.dump_int("integer", 20); + formatter.dump_float("float", 20.0); + formatter.dump_string("string", "string"); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "| 20 | 20 | string |\n" + "+----------+--------+---------+\n"; + + formatter.flush(sout); + EXPECT_EQ(cmp, sout.str()); +} + +TEST(tableformatter, multiflush) +{ + std::stringstream sout1; + std::stringstream sout2; + TableFormatter formatter; + formatter.dump_int("integer", 10); + formatter.dump_float("float", 10.0); + formatter.dump_string("string", "string"); + formatter.flush(sout1); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n"; + + EXPECT_EQ(cmp, sout1.str()); + + formatter.dump_int("integer", 20); + formatter.dump_float("float", 20.0); + formatter.dump_string("string", "string"); + formatter.flush(sout2); + + cmp = "" + "| 20 | 20 | string |\n" + "+----------+--------+---------+\n"; + + EXPECT_EQ(cmp, sout2.str()); + +} + +TEST(tableformatter, multireset) +{ + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer", 10); + formatter.dump_float("float", 10.0); + formatter.dump_string("string", "string"); + formatter.flush(sout); + formatter.reset(); + formatter.dump_int("integer", 20); + formatter.dump_float("float", 20.0); + formatter.dump_string("string", "string"); + formatter.flush(sout); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 20 | 20 | string |\n" + "+----------+--------+---------+\n"; + + EXPECT_EQ(cmp, sout.str()); +} + +TEST(tableformatter, changingheaderlength) +{ + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer", 10); + formatter.dump_float("float", 10.0); + formatter.dump_string("string", "string"); + formatter.flush(sout); + formatter.dump_int("integer", 20); + formatter.dump_float("float", 20.0); + formatter.dump_string("string", "stringstring"); + formatter.flush(sout); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n" + "+----------+--------+---------------+\n" + "| integer | float | string |\n" + "+----------+--------+---------------+\n" + "| 20 | 20 | stringstring |\n" + "+----------+--------+---------------+\n"; + + EXPECT_EQ(cmp, sout.str()); +} + +TEST(tableformatter, changingheader) +{ + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer", 10); + formatter.dump_float("float", 10.0); + formatter.dump_string("string", "string"); + formatter.flush(sout); + formatter.dump_int("longinteger", 20); + formatter.dump_float("double", 20.0); + formatter.dump_string("char*", "stringstring"); + formatter.flush(sout); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n" + "+--------------+---------+---------------+\n" + "| longinteger | double | char* |\n" + "+--------------+---------+---------------+\n" + "| 20 | 20 | stringstring |\n" + "+--------------+---------+---------------+\n"; + + EXPECT_EQ(cmp, sout.str()); +} + +TEST(tableformatter, extendingheader) +{ + std::stringstream sout; + TableFormatter formatter; + formatter.dump_int("integer", 10); + formatter.dump_float("float", 10.0); + formatter.dump_string("string", "string"); + formatter.flush(sout); + formatter.dump_int("integer", 20); + formatter.dump_float("float", 20.0); + formatter.dump_string("string", "string"); + formatter.dump_string("char*", "abcde"); + formatter.flush(sout); + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n" + "+----------+--------+---------+--------+\n" + "| integer | float | string | char* |\n" + "+----------+--------+---------+--------+\n" + "| 20 | 20 | string | abcde |\n" + "+----------+--------+---------+--------+\n"; + + EXPECT_EQ(cmp, sout.str()); +} + +TEST(tableformatter, stream) +{ + std::stringstream sout; + TableFormatter* formatter = (TableFormatter*) Formatter::create("table"); + formatter->dump_stream("integer") << 10; + formatter->dump_stream("float") << 10.0; + formatter->dump_stream("string") << "string"; + formatter->flush(sout); + delete formatter; + + std::string cmp = "" + "+----------+--------+---------+\n" + "| integer | float | string |\n" + "+----------+--------+---------+\n" + "| 10 | 10 | string |\n" + "+----------+--------+---------+\n"; + + EXPECT_EQ(cmp, sout.str()); +} + +TEST(tableformatter, multiline_keyval) +{ + std::stringstream sout; + TableFormatter* formatter = (TableFormatter*) Formatter::create("table-kv"); + formatter->dump_int("integer", 10); + formatter->dump_float("float", 10.0); + formatter->dump_string("string", "string"); + formatter->dump_int("integer", 20); + formatter->dump_float("float", 20.0); + formatter->dump_string("string", "string"); + formatter->flush(sout); + delete formatter; + + std::string cmp = "" + "key::integer=\"10\" key::float=\"10\" key::string=\"string\" \n" + "key::integer=\"20\" key::float=\"20\" key::string=\"string\" \n"; + + EXPECT_EQ(cmp, sout.str()); +} + +/* + * Local Variables: + * compile-command: "cd ../.. ; make -j4 && + * make unittest_tableformatter && + * ./unittest_tableformatter + * ' + * End: + */ + + + diff --git a/src/test/common/test_time.cc b/src/test/common/test_time.cc new file mode 100644 index 000000000..bc19ba573 --- /dev/null +++ b/src/test/common/test_time.cc @@ -0,0 +1,235 @@ + +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net> + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <ctime> + +#include "common/ceph_time.h" +#include "include/rados.h" +#include "gtest/gtest.h" +#include "include/stringify.h" + +using namespace std; + +using ceph::real_clock; +using ceph::real_time; + +using ceph::real_clock; +using ceph::real_time; + +using ceph::coarse_real_clock; +using ceph::coarse_mono_clock; + +using ceph::timespan; +using ceph::signedspan; + +using std::chrono::seconds; +using std::chrono::microseconds; +using std::chrono::nanoseconds; + +static_assert(!real_clock::is_steady, "ceph::real_clock must not be steady."); +static_assert(!coarse_real_clock::is_steady, + "ceph::coarse_real_clock must not be steady."); + +static_assert(mono_clock::is_steady, "ceph::mono_clock must be steady."); +static_assert(coarse_mono_clock::is_steady, + "ceph::coarse_mono_clock must be steady."); + +// Before this file was written. +static constexpr uint32_t bs = 1440701569; +static constexpr uint32_t bns = 123456789; +static constexpr uint32_t bus = 123456; +static constexpr time_t btt = bs; +static constexpr struct timespec bts = { bs, bns }; +static struct ceph_timespec bcts = { ceph_le32(bs), ceph_le32(bns) }; +static constexpr struct timeval btv = { bs, bus }; +static constexpr double bd = bs + ((double)bns / 1000000000.); + +template<typename Clock> +static void system_clock_sanity() { + static const typename Clock::time_point brt(seconds(bs) + nanoseconds(bns)); + const typename Clock::time_point now(Clock::now()); + + ASSERT_GT(now, brt); + + ASSERT_GT(Clock::to_time_t(now), btt); + + ASSERT_GT(Clock::to_timespec(now).tv_sec, bts.tv_sec); + ASSERT_LT(Clock::to_timespec(now).tv_nsec, 1000000000L); + + ASSERT_GT(Clock::to_ceph_timespec(now).tv_sec, bcts.tv_sec); + ASSERT_LT(Clock::to_ceph_timespec(now).tv_nsec, 1000000000UL); + + ASSERT_GT(Clock::to_timeval(now).tv_sec, btv.tv_sec); + ASSERT_LT(Clock::to_timeval(now).tv_usec, 1000000L); +} + +template<typename Clock> +static void system_clock_conversions() { + static typename Clock::time_point brt(seconds(bs) + + nanoseconds(bns)); + + ASSERT_EQ(Clock::to_time_t(brt), btt); + ASSERT_EQ(Clock::from_time_t(btt) + nanoseconds(bns), brt); + + { + const struct timespec tts = Clock::to_timespec(brt); + ASSERT_EQ(tts.tv_sec, bts.tv_sec); + ASSERT_EQ(tts.tv_nsec, bts.tv_nsec); + } + ASSERT_EQ(Clock::from_timespec(bts), brt); + { + struct timespec tts; + Clock::to_timespec(brt, tts); + ASSERT_EQ(tts.tv_sec, bts.tv_sec); + ASSERT_EQ(tts.tv_nsec, bts.tv_nsec); + } + + { + const struct ceph_timespec tcts = Clock::to_ceph_timespec(brt); + ASSERT_EQ(tcts.tv_sec, bcts.tv_sec); + ASSERT_EQ(tcts.tv_nsec, bcts.tv_nsec); + } + ASSERT_EQ(Clock::from_ceph_timespec(bcts), brt); + { + struct ceph_timespec tcts; + Clock::to_ceph_timespec(brt, tcts); + ASSERT_EQ(tcts.tv_sec, bcts.tv_sec); + ASSERT_EQ(tcts.tv_nsec, bcts.tv_nsec); + } + + { + const struct timeval ttv = Clock::to_timeval(brt); + ASSERT_EQ(ttv.tv_sec, btv.tv_sec); + ASSERT_EQ(ttv.tv_usec, btv.tv_usec); + } + ASSERT_EQ(Clock::from_timeval(btv), brt - nanoseconds(bns - bus * 1000)); + { + struct timeval ttv; + Clock::to_timeval(brt, ttv); + ASSERT_EQ(ttv.tv_sec, btv.tv_sec); + ASSERT_EQ(ttv.tv_usec, btv.tv_usec); + } + + ASSERT_EQ(Clock::to_double(brt), bd); + // Fudge factor + ASSERT_LT(std::abs((Clock::from_double(bd) - brt).count()), 30); +} + +TEST(RealClock, Sanity) { + system_clock_sanity<real_clock>(); +} + + +TEST(RealClock, Conversions) { + system_clock_conversions<real_clock>(); +} + +TEST(CoarseRealClock, Sanity) { + system_clock_sanity<coarse_real_clock>(); +} + + +TEST(CoarseRealClock, Conversions) { + system_clock_conversions<coarse_real_clock>(); +} + +TEST(TimePoints, SignedSubtraciton) { + ceph::real_time rta(std::chrono::seconds(3)); + ceph::real_time rtb(std::chrono::seconds(5)); + + ceph::coarse_real_time crta(std::chrono::seconds(3)); + ceph::coarse_real_time crtb(std::chrono::seconds(5)); + + ceph::mono_time mta(std::chrono::seconds(3)); + ceph::mono_time mtb(std::chrono::seconds(5)); + + ceph::coarse_mono_time cmta(std::chrono::seconds(3)); + ceph::coarse_mono_time cmtb(std::chrono::seconds(5)); + + ASSERT_LT(rta - rtb, ceph::signedspan::zero()); + ASSERT_LT((rta - rtb).count(), 0); + ASSERT_GT(rtb - rta, ceph::signedspan::zero()); + ASSERT_GT((rtb - rta).count(), 0); + + ASSERT_LT(crta - crtb, ceph::signedspan::zero()); + ASSERT_LT((crta - crtb).count(), 0); + ASSERT_GT(crtb - crta, ceph::signedspan::zero()); + ASSERT_GT((crtb - crta).count(), 0); + + ASSERT_LT(mta - mtb, ceph::signedspan::zero()); + ASSERT_LT((mta - mtb).count(), 0); + ASSERT_GT(mtb - mta, ceph::signedspan::zero()); + ASSERT_GT((mtb - mta).count(), 0); + + ASSERT_LT(cmta - cmtb, ceph::signedspan::zero()); + ASSERT_LT((cmta - cmtb).count(), 0); + ASSERT_GT(cmtb - cmta, ceph::signedspan::zero()); + ASSERT_GT((cmtb - cmta).count(), 0); +} + +TEST(TimePoints, stringify) { + ceph::real_clock::time_point tp(seconds(1556122013) + nanoseconds(39923122)); + string s = stringify(tp); + ASSERT_EQ(s.size(), strlen("2019-04-24T11:06:53.039923-0500")); + ASSERT_TRUE(s[26] == '-' || s[26] == '+'); + ASSERT_EQ(s.substr(0, 9), "2019-04-2"); + + ceph::coarse_real_clock::time_point ctp(seconds(1556122013) + + nanoseconds(399000000)); + s = stringify(ctp); + ASSERT_EQ(s.size(), strlen("2019-04-24T11:06:53.399000-0500")); + ASSERT_TRUE(s[26] == '-' || s[26] == '+'); + ASSERT_EQ(s.substr(0, 9), "2019-04-2"); +} + +namespace { + template<typename Rep, typename Period> + std::string to_string(const chrono::duration<Rep, Period>& t) + { + std::ostringstream ss; + ss << t; + return ss.str(); + } + + void float_format_eq(string_view lhs, + string_view rhs, + unsigned precision) + { + const float TOLERANCE = 10.0F / pow(10.0F, static_cast<float>(precision)); + ASSERT_FALSE(lhs.empty()); + ASSERT_EQ(lhs.back(), 's'); + float lhs_v = std::stof(string{lhs, 0, lhs.find('s')}); + ASSERT_NE(lhs.npos, lhs.find('.')); + ASSERT_EQ(precision, lhs.find('s') - lhs.find('.') - 1); + + ASSERT_FALSE(rhs.empty()); + ASSERT_EQ(rhs.back(), 's'); + float rhs_v = std::stof(string{rhs, 0, rhs.find('s')}); + EXPECT_NEAR(lhs_v, rhs_v, TOLERANCE); + ASSERT_NE(rhs.npos, rhs.find('.')); + EXPECT_EQ(precision, rhs.find('s') - rhs.find('.') - 1); + } +} + +TEST(TimeDurations, print) { + float_format_eq("0.123456700s", + to_string(std::chrono::duration_cast<ceph::timespan>(0.1234567s)), + 9); + float_format_eq("-0.123456700s", + to_string(std::chrono::duration_cast<ceph::signedspan>(-0.1234567s)), + 9); + EXPECT_EQ("42s", to_string(42s)); + float_format_eq("0.123000000s", to_string(123ms), 9); +} diff --git a/src/test/common/test_url_escape.cc b/src/test/common/test_url_escape.cc new file mode 100644 index 000000000..6c27b64da --- /dev/null +++ b/src/test/common/test_url_escape.cc @@ -0,0 +1,36 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "common/url_escape.h" + +#include "gtest/gtest.h" + +TEST(url_escape, escape) { + ASSERT_EQ(url_escape("foo bar"), std::string("foo%20bar")); + ASSERT_EQ(url_escape("foo\nbar"), std::string("foo%0abar")); +} + +TEST(url_escape, unescape) { + ASSERT_EQ(url_unescape("foo%20bar"), std::string("foo bar")); + ASSERT_EQ(url_unescape("foo%0abar"), std::string("foo\nbar")); + ASSERT_EQ(url_unescape("%20"), std::string(" ")); + ASSERT_EQ(url_unescape("\0%20"), std::string("\0 ")); + ASSERT_EQ(url_unescape("\x01%20"), std::string("\x01 ")); +} + +TEST(url_escape, all_chars) { + std::string a; + for (unsigned j=0; j<256; ++j) { + a.push_back((char)j); + } + std::string b = url_escape(a); + std::cout << "escaped: " << b << std::endl; + ASSERT_EQ(a, url_unescape(b)); +} + +TEST(url_escape, invalid) { + ASSERT_THROW(url_unescape("foo%xx"), std::runtime_error); + ASSERT_THROW(url_unescape("foo%%"), std::runtime_error); + ASSERT_THROW(url_unescape("foo%"), std::runtime_error); + ASSERT_THROW(url_unescape("foo%0"), std::runtime_error); +} diff --git a/src/test/common/test_util.cc b/src/test/common/test_util.cc new file mode 100644 index 000000000..91ac771f8 --- /dev/null +++ b/src/test/common/test_util.cc @@ -0,0 +1,42 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2011 New Dream Network + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License version 2, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <filesystem> + +#include "gtest/gtest.h" +#include "common/ceph_context.h" +#include "include/util.h" + +using namespace std; + +namespace fs = std::filesystem; + +#if defined(__linux__) +TEST(util, collect_sys_info) +{ + if (!fs::exists("/etc/os-release")) { + GTEST_SKIP() << "skipping as '/etc/os-release' does not exist"; + } + + map<string, string> sys_info; + + CephContext *cct = (new CephContext(CEPH_ENTITY_TYPE_CLIENT))->get(); + collect_sys_info(&sys_info, cct); + + ASSERT_TRUE(sys_info.find("distro") != sys_info.end()); + ASSERT_TRUE(sys_info.find("distro_description") != sys_info.end()); + + cct->put(); +} +#endif diff --git a/src/test/common/test_weighted_priority_queue.cc b/src/test/common/test_weighted_priority_queue.cc new file mode 100644 index 000000000..263fc4cb4 --- /dev/null +++ b/src/test/common/test_weighted_priority_queue.cc @@ -0,0 +1,240 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "gtest/gtest.h" +#include "common/Formatter.h" +#include "common/WeightedPriorityQueue.h" + +#include <numeric> +#include <vector> +#include <map> +#include <list> +#include <tuple> + +#define CEPH_OP_CLASS_STRICT 0 +#define CEPH_OP_CLASS_NORMAL 0 +#define CEPH_OP_QUEUE_BACK 0 +#define CEPH_OP_QUEUE_FRONT 0 + +class WeightedPriorityQueueTest : public testing::Test +{ +protected: + typedef unsigned Klass; + // tuple<Prio, Klass, OpID> so that we can verfiy the op + typedef std::tuple<unsigned, unsigned, unsigned> Item; + typedef unsigned Prio; + typedef unsigned Kost; + typedef WeightedPriorityQueue<Item, Klass> WQ; + // Simulate queue structure + typedef std::list<std::pair<Kost, Item> > ItemList; + typedef std::map<Klass, ItemList> KlassItem; + typedef std::map<Prio, KlassItem> LQ; + typedef std::list<Item> Removed; + const unsigned max_prios = 5; // (0-4) * 64 + const unsigned klasses = 37; // Make prime to help get good coverage + + void fill_queue(WQ &wq, LQ &strictq, LQ &normq, + unsigned item_size, bool randomize = false) { + unsigned p, k, c, o, op_queue, fob; + for (unsigned i = 1; i <= item_size; ++i) { + // Choose priority, class, cost and 'op' for this op. + if (randomize) { + p = (rand() % max_prios) * 64; + k = rand() % klasses; + c = rand() % (1<<22); // 4M cost + // Make some of the costs 0, but make sure small costs + // still work ok. + if (c > (1<<19) && c < (1<<20)) { + c = 0; + } + op_queue = rand() % 10; + fob = rand() % 10; + } else { + p = (i % max_prios) * 64; + k = i % klasses; + c = (i % 8 == 0 || i % 16 == 0) ? 0 : 1 << (i % 23); + op_queue = i % 7; // Use prime numbers to + fob = i % 11; // get better coverage + } + o = rand() % (1<<16); + // Choose how to enqueue this op. + switch (op_queue) { + case 6 : + // Strict Queue + if (fob == 4) { + // Queue to the front. + strictq[p][k].push_front(std::make_pair( + c, std::make_tuple(p, k, o))); + wq.enqueue_strict_front(Klass(k), p, std::make_tuple(p, k, o)); + } else { + //Queue to the back. + strictq[p][k].push_back(std::make_pair( + c, std::make_tuple(p, k, o))); + wq.enqueue_strict(Klass(k), p, std::make_tuple(p, k, o)); + } + break; + default: + // Normal queue + if (fob == 4) { + // Queue to the front. + normq[p][k].push_front(std::make_pair( + c, std::make_tuple(p, k, o))); + wq.enqueue_front(Klass(k), p, c, std::make_tuple(p, k, o)); + } else { + //Queue to the back. + normq[p][k].push_back(std::make_pair( + c, std::make_tuple(p, k, o))); + wq.enqueue(Klass(k), p, c, std::make_tuple(p, k, o)); + } + break; + } + } + } + void test_queue(unsigned item_size, bool randomize = false) { + // Due to the WRR queue having a lot of probabilistic logic + // we can't determine the exact order OPs will be dequeued. + // However, the queue should not dequeue a priority out of + // order. It should also dequeue the strict priority queue + // first and in order. In both the strict and normal queues + // push front and back should be respected. Here we keep + // track of the ops queued and make sure they dequeue + // correctly. + + // Set up local tracking queues + WQ wq(0, 0); + LQ strictq, normq; + fill_queue(wq, strictq, normq, item_size, randomize); + // Test that the queue is dequeuing properly. + typedef std::map<unsigned, unsigned> LastKlass; + LastKlass last_strict, last_norm; + while (!(wq.empty())) { + Item r = wq.dequeue(); + if (!(strictq.empty())) { + // Check that there are no higher priorities + // in the strict queue. + LQ::reverse_iterator ri = strictq.rbegin(); + EXPECT_EQ(std::get<0>(r), ri->first); + // Check that if there are multiple classes in a priority + // that it is not dequeueing the same class each time. + LastKlass::iterator si = last_strict.find(std::get<0>(r)); + if (strictq[std::get<0>(r)].size() > 1 && si != last_strict.end()) { + EXPECT_NE(std::get<1>(r), si->second); + } + last_strict[std::get<0>(r)] = std::get<1>(r); + + Item t = strictq[std::get<0>(r)][std::get<1>(r)].front().second; + EXPECT_EQ(std::get<2>(r), std::get<2>(t)); + strictq[std::get<0>(r)][std::get<1>(r)].pop_front(); + if (strictq[std::get<0>(r)][std::get<1>(r)].empty()) { + strictq[std::get<0>(r)].erase(std::get<1>(r)); + } + if (strictq[std::get<0>(r)].empty()) { + strictq.erase(std::get<0>(r)); + } + } else { + // Check that if there are multiple classes in a priority + // that it is not dequeueing the same class each time. + LastKlass::iterator si = last_norm.find(std::get<0>(r)); + if (normq[std::get<0>(r)].size() > 1 && si != last_norm.end()) { + EXPECT_NE(std::get<1>(r), si->second); + } + last_norm[std::get<0>(r)] = std::get<1>(r); + + Item t = normq[std::get<0>(r)][std::get<1>(r)].front().second; + EXPECT_EQ(std::get<2>(r), std::get<2>(t)); + normq[std::get<0>(r)][std::get<1>(r)].pop_front(); + if (normq[std::get<0>(r)][std::get<1>(r)].empty()) { + normq[std::get<0>(r)].erase(std::get<1>(r)); + } + if (normq[std::get<0>(r)].empty()) { + normq.erase(std::get<0>(r)); + } + } + } + } + + void SetUp() override { + srand(time(0)); + } + void TearDown() override { + } +}; + +TEST_F(WeightedPriorityQueueTest, wpq_size){ + WQ wq(0, 0); + EXPECT_TRUE(wq.empty()); + EXPECT_EQ(0u, wq.get_size_slow()); + + // Test the strict queue size. + for (unsigned i = 1; i < 5; ++i) { + wq.enqueue_strict(Klass(i),i, std::make_tuple(i, i, i)); + EXPECT_FALSE(wq.empty()); + EXPECT_EQ(i, wq.get_size_slow()); + } + // Test the normal queue size. + for (unsigned i = 5; i < 10; ++i) { + wq.enqueue(Klass(i), i, i, std::make_tuple(i, i, i)); + EXPECT_FALSE(wq.empty()); + EXPECT_EQ(i, wq.get_size_slow()); + } + // Test that as both queues are emptied + // the size is correct. + for (unsigned i = 8; i >0; --i) { + wq.dequeue(); + EXPECT_FALSE(wq.empty()); + EXPECT_EQ(i, wq.get_size_slow()); + } + wq.dequeue(); + EXPECT_TRUE(wq.empty()); + EXPECT_EQ(0u, wq.get_size_slow()); +} + +TEST_F(WeightedPriorityQueueTest, wpq_test_static) { + test_queue(1000); +} + +TEST_F(WeightedPriorityQueueTest, wpq_test_random) { + test_queue(rand() % 500 + 500, true); +} + +TEST_F(WeightedPriorityQueueTest, wpq_test_remove_by_class_null) { + WQ wq(0, 0); + LQ strictq, normq; + unsigned num_items = 10; + fill_queue(wq, strictq, normq, num_items); + Removed wq_removed; + // Pick a klass that was not enqueued + wq.remove_by_class(klasses + 1, &wq_removed); + EXPECT_EQ(0u, wq_removed.size()); +} + +TEST_F(WeightedPriorityQueueTest, wpq_test_remove_by_class) { + WQ wq(0, 0); + LQ strictq, normq; + unsigned num_items = 1000; + fill_queue(wq, strictq, normq, num_items); + unsigned num_to_remove = 0; + const Klass k = 5; + // Find how many ops are in the class + for (LQ::iterator it = strictq.begin(); + it != strictq.end(); ++it) { + num_to_remove += it->second[k].size(); + } + for (LQ::iterator it = normq.begin(); + it != normq.end(); ++it) { + num_to_remove += it->second[k].size(); + } + Removed wq_removed; + wq.remove_by_class(k, &wq_removed); + // Check that the right ops were removed. + EXPECT_EQ(num_to_remove, wq_removed.size()); + EXPECT_EQ(num_items - num_to_remove, wq.get_size_slow()); + for (Removed::iterator it = wq_removed.begin(); + it != wq_removed.end(); ++it) { + EXPECT_EQ(k, std::get<1>(*it)); + } + // Check that none were missed + while (!(wq.empty())) { + EXPECT_NE(k, std::get<1>(wq.dequeue())); + } +} diff --git a/src/test/common/test_xmlformatter.cc b/src/test/common/test_xmlformatter.cc new file mode 100644 index 000000000..9ac6dde45 --- /dev/null +++ b/src/test/common/test_xmlformatter.cc @@ -0,0 +1,165 @@ +#include "gtest/gtest.h" + +#include "common/Formatter.h" +#include <sstream> +#include <string> + +using namespace ceph; + + +TEST(xmlformatter, oneline) +{ + + std::stringstream sout; + XMLFormatter formatter; + formatter.dump_int("integer", 10); + formatter.dump_float("float", 10.0); + formatter.dump_string("string", "string"); + formatter.flush(sout); + std::string cmp = "<integer>10</integer><float>10</float><string>string</string>"; + EXPECT_EQ(cmp, sout.str()); +} + +TEST(xmlformatter, multiline) +{ + std::stringstream sout; + XMLFormatter formatter; + formatter.dump_int("integer", 10); + formatter.dump_float("float", 10.0); + formatter.dump_string("string", "string"); + formatter.dump_int("integer", 20); + formatter.dump_float("float", 20.0); + formatter.dump_string("string", "string"); + + std::string cmp = "" + "<integer>10</integer><float>10</float><string>string</string>" + "<integer>20</integer><float>20</float><string>string</string>"; + + formatter.flush(sout); + EXPECT_EQ(cmp, sout.str()); +} + +TEST(xmlformatter, multiflush) +{ + std::stringstream sout1; + std::stringstream sout2; + XMLFormatter formatter; + formatter.dump_int("integer", 10); + formatter.dump_float("float", 10.0); + formatter.dump_string("string", "string"); + formatter.flush(sout1); + + std::string cmp = "" + "<integer>10</integer>" + "<float>10</float>" + "<string>string</string>"; + + EXPECT_EQ(cmp, sout1.str()); + + formatter.dump_int("integer", 20); + formatter.dump_float("float", 20.0); + formatter.dump_string("string", "string"); + formatter.flush(sout2); + + cmp = "" + "<integer>20</integer>" + "<float>20</float>" + "<string>string</string>"; + + EXPECT_EQ(cmp, sout2.str()); +} + +TEST(xmlformatter, pretty) +{ + std::stringstream sout; + XMLFormatter formatter( + true, // pretty + false, // lowercased + false); // underscored + formatter.open_object_section("xml"); + formatter.dump_int("Integer", 10); + formatter.dump_float("Float", 10.0); + formatter.dump_string("String", "String"); + formatter.close_section(); + formatter.flush(sout); + std::string cmp = "" + "<xml>\n" + " <Integer>10</Integer>\n" + " <Float>10</Float>\n" + " <String>String</String>\n" + "</xml>\n\n"; + EXPECT_EQ(cmp, sout.str()); +} + +TEST(xmlformatter, lowercased) +{ + std::stringstream sout; + XMLFormatter formatter( + false, // pretty + true, // lowercased + false); // underscored + formatter.dump_int("Integer", 10); + formatter.dump_float("Float", 10.0); + formatter.dump_string("String", "String"); + formatter.flush(sout); + std::string cmp = "" + "<integer>10</integer>" + "<float>10</float>" + "<string>String</string>"; + EXPECT_EQ(cmp, sout.str()); +} + +TEST(xmlformatter, underscored) +{ + std::stringstream sout; + XMLFormatter formatter( + false, // pretty + false, // lowercased + true); // underscored + formatter.dump_int("Integer Item", 10); + formatter.dump_float("Float Item", 10.0); + formatter.dump_string("String Item", "String"); + formatter.flush(sout); + std::string cmp = "" + "<Integer_Item>10</Integer_Item>" + "<Float_Item>10</Float_Item>" + "<String_Item>String</String_Item>"; + + EXPECT_EQ(cmp, sout.str()); +} + +TEST(xmlformatter, lowercased_underscored) +{ + std::stringstream sout; + XMLFormatter formatter( + false, // pretty + true, // lowercased + true); // underscored + formatter.dump_int("Integer Item", 10); + formatter.dump_float("Float Item", 10.0); + formatter.dump_string("String Item", "String"); + formatter.flush(sout); + std::string cmp = "" + "<integer_item>10</integer_item>" + "<float_item>10</float_item>" + "<string_item>String</string_item>"; + EXPECT_EQ(cmp, sout.str()); +} + +TEST(xmlformatter, pretty_lowercased_underscored) +{ + std::stringstream sout; + XMLFormatter formatter( + true, // pretty + true, // lowercased + true); // underscored + formatter.dump_int("Integer Item", 10); + formatter.dump_float("Float Item", 10.0); + formatter.dump_string("String Item", "String"); + formatter.flush(sout); + std::string cmp = "" + "<integer_item>10</integer_item>\n" + "<float_item>10</float_item>\n" + "<string_item>String</string_item>\n\n"; + EXPECT_EQ(cmp, sout.str()); +} |