summaryrefslogtreecommitdiffstats
path: root/src/test/common
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/test/common/CMakeLists.txt392
-rw-r--r--src/test/common/ObjectContents.cc128
-rw-r--r--src/test/common/ObjectContents.h122
-rw-r--r--src/test/common/Readahead.cc132
-rw-r--r--src/test/common/Throttle.cc386
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/autriche.nvme0n117
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/autriche.nvme0n1.devid1
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/cpach.sdn53
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/cpach.sdn.devid1
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/erwan.v1.sda31
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/erwan.v1.sda.devid1
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/erwan.v1.sdb32
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/erwan.v1.sdb.devid1
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/erwan131
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/gnit.nvme0n125
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/gnit.nvme0n1.devid1
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/gnit.sda46
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/gnit.sda.devid1
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/mira055.sda28
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/mira055.sda.devid1
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/mira055.sdb28
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/mira055.sdb.devid1
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/mira055.sde28
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/mira055.sde.devid1
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/reesi001.nvme0n116
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/reesi001.nvme0n1.devid1
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/stud.nvme0n129
-rw-r--r--src/test/common/blkdev-udevadm-info-samples/stud.nvme0n1.devid1
-rw-r--r--src/test/common/dns_messages.h100
-rw-r--r--src/test/common/dns_resolve.cc263
-rw-r--r--src/test/common/get_command_descriptions.cc131
-rw-r--r--src/test/common/histogram.cc129
-rw-r--r--src/test/common/test_allocate_unique.cc97
-rw-r--r--src/test/common/test_async_completion.cc256
-rw-r--r--src/test/common/test_async_shared_mutex.cc428
-rw-r--r--src/test/common/test_back_trace.cc44
-rw-r--r--src/test/common/test_bit_vector.cc308
-rw-r--r--src/test/common/test_blkdev.cc114
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/add_random1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_granularity1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_max_bytes1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/discard_zeroes_data1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/hw_sector_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/fifo_batch1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/front_merges1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/read_expire1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/write_expire1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iosched/writes_starved1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/iostats1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/logical_block_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_hw_sectors_kb1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_integrity_segments1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_sectors_kb1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_segment_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/max_segments1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/minimum_io_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/nomerges1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/nr_requests1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/optimal_io_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/physical_block_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/read_ahead_kb1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/rotational1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/rq_affinity1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/scheduler1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/cciss!c0d1/queue/write_same_max_bytes1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/bar0
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/dev0
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/device/model1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/foo0
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/add_random1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_granularity1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_max_bytes1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/discard_zeroes_data1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/hw_sector_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/fifo_batch1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/front_merges1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/read_expire1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/write_expire1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iosched/writes_starved1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/iostats1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/logical_block_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_hw_sectors_kb1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_integrity_segments1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_sectors_kb1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_segment_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/max_segments1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/minimum_io_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/nomerges1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/nr_requests1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/optimal_io_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/physical_block_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/read_ahead_kb1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/rotational1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/rq_affinity1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/scheduler1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sda/queue/write_same_max_bytes1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/bar0
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/dev0
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/device/model1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/foo0
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/add_random1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_granularity1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_max_bytes1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/discard_zeroes_data1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/hw_sector_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/fifo_batch1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/front_merges1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/read_expire1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/write_expire1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iosched/writes_starved1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/iostats1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/logical_block_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_hw_sectors_kb1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_integrity_segments1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_sectors_kb1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_segment_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/max_segments1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/minimum_io_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/nomerges1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/nr_requests1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/optimal_io_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/physical_block_size1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/queue1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/read_ahead_kb1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/rotational1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/rq_affinity1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/scheduler1
-rw-r--r--src/test/common/test_blkdev_sys_block/sys/block/sdb/queue/write_same_max_bytes1
-rw-r--r--src/test/common/test_blocked_completion.cc237
-rw-r--r--src/test/common/test_bloom_filter.cc323
-rw-r--r--src/test/common/test_bounded_key_counter.cc200
-rw-r--r--src/test/common/test_cdc.cc163
-rw-r--r--src/test/common/test_ceph_timer.cc163
-rw-r--r--src/test/common/test_config.cc313
-rw-r--r--src/test/common/test_context.cc145
-rw-r--r--src/test/common/test_convenience.cc69
-rw-r--r--src/test/common/test_counter.cc40
-rw-r--r--src/test/common/test_crc32c.cc365
-rw-r--r--src/test/common/test_fair_mutex.cc68
-rw-r--r--src/test/common/test_fault_injector.cc248
-rw-r--r--src/test/common/test_global_doublefree.cc30
-rw-r--r--src/test/common/test_hobject.cc11
-rw-r--r--src/test/common/test_hostname.cc71
-rw-r--r--src/test/common/test_interval_map.cc337
-rw-r--r--src/test/common/test_interval_set.cc600
-rw-r--r--src/test/common/test_intrusive_lru.cc208
-rw-r--r--src/test/common/test_iso_8601.cc60
-rw-r--r--src/test/common/test_journald_logger.cc41
-rw-r--r--src/test/common/test_json_formattable.cc453
-rw-r--r--src/test/common/test_json_formatter.cc81
-rw-r--r--src/test/common/test_lockdep.cc74
-rw-r--r--src/test/common/test_lru.cc158
-rw-r--r--src/test/common/test_lruset.cc109
-rw-r--r--src/test/common/test_mclock_priority_queue.cc320
-rw-r--r--src/test/common/test_mutex_debug.cc101
-rw-r--r--src/test/common/test_numa.cc72
-rw-r--r--src/test/common/test_option.cc73
-rw-r--r--src/test/common/test_perf_counters_key.cc129
-rw-r--r--src/test/common/test_perf_histogram.cc247
-rw-r--r--src/test/common/test_pretty_binary.cc50
-rw-r--r--src/test/common/test_prioritized_queue.cc206
-rw-r--r--src/test/common/test_rabin_chunk.cc151
-rw-r--r--src/test/common/test_random.cc246
-rw-r--r--src/test/common/test_safe_io.cc37
-rw-r--r--src/test/common/test_shared_cache.cc400
-rw-r--r--src/test/common/test_sharedptr_registry.cc330
-rw-r--r--src/test/common/test_shunique_lock.cc575
-rw-r--r--src/test/common/test_sloppy_crc_map.cc116
-rw-r--r--src/test/common/test_split.cc119
-rw-r--r--src/test/common/test_static_ptr.cc216
-rw-r--r--src/test/common/test_str_map.cc89
-rw-r--r--src/test/common/test_tableformatter.cc263
-rw-r--r--src/test/common/test_time.cc235
-rw-r--r--src/test/common/test_url_escape.cc36
-rw-r--r--src/test/common/test_util.cc42
-rw-r--r--src/test/common/test_weighted_priority_queue.cc240
-rw-r--r--src/test/common/test_xmlformatter.cc165
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(&current_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(&current_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 &registry;
+ 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 &registry, 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());
+}