|
|
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
|
|
|
|
|
|
#include "util/heap.h"
|
|
|
|
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
|
|
#include <climits>
|
|
|
|
#include <queue>
|
|
|
|
#include <random>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
#include "port/stack_trace.h"
|
|
|
|
|
|
|
|
#ifndef GFLAGS
|
|
|
|
const int64_t FLAGS_iters = 100000;
|
|
|
|
#else
|
|
|
|
#include "util/gflags_compat.h"
|
|
|
|
DEFINE_int64(iters, 100000, "number of pseudo-random operations in each test");
|
|
|
|
#endif // GFLAGS
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compares the custom heap implementation in util/heap.h against
|
|
|
|
* std::priority_queue on a pseudo-random sequence of operations.
|
|
|
|
*/
|
|
|
|
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
|
|
|
|
using HeapTestValue = uint64_t;
|
|
|
|
using Params = std::tuple<size_t, HeapTestValue, int64_t>;
|
|
|
|
|
|
|
|
class HeapTest : public ::testing::TestWithParam<Params> {
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_P(HeapTest, Test) {
|
|
|
|
// This test performs the same pseudorandom sequence of operations on a
|
|
|
|
// BinaryHeap and an std::priority_queue, comparing output. The three
|
|
|
|
// possible operations are insert, replace top and pop.
|
|
|
|
//
|
|
|
|
// Insert is chosen slightly more often than the others so that the size of
|
|
|
|
// the heap slowly grows. Once the size heats the MAX_HEAP_SIZE limit, we
|
|
|
|
// disallow inserting until the heap becomes empty, testing the "draining"
|
|
|
|
// scenario.
|
|
|
|
|
|
|
|
const auto MAX_HEAP_SIZE = std::get<0>(GetParam());
|
|
|
|
const auto MAX_VALUE = std::get<1>(GetParam());
|
|
|
|
const auto RNG_SEED = std::get<2>(GetParam());
|
|
|
|
|
|
|
|
BinaryHeap<HeapTestValue> heap;
|
|
|
|
std::priority_queue<HeapTestValue> ref;
|
|
|
|
|
|
|
|
std::mt19937 rng(static_cast<unsigned int>(RNG_SEED));
|
|
|
|
std::uniform_int_distribution<HeapTestValue> value_dist(0, MAX_VALUE);
|
|
|
|
int ndrains = 0;
|
|
|
|
bool draining = false; // hit max size, draining until we empty the heap
|
|
|
|
size_t size = 0;
|
|
|
|
for (int64_t i = 0; i < FLAGS_iters; ++i) {
|
|
|
|
if (size == 0) {
|
|
|
|
draining = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!draining &&
|
|
|
|
(size == 0 || std::bernoulli_distribution(0.4)(rng))) {
|
|
|
|
// insert
|
|
|
|
HeapTestValue val = value_dist(rng);
|
|
|
|
heap.push(val);
|
|
|
|
ref.push(val);
|
|
|
|
++size;
|
|
|
|
if (size == MAX_HEAP_SIZE) {
|
|
|
|
draining = true;
|
|
|
|
++ndrains;
|
|
|
|
}
|
|
|
|
} else if (std::bernoulli_distribution(0.5)(rng)) {
|
|
|
|
// replace top
|
|
|
|
HeapTestValue val = value_dist(rng);
|
|
|
|
heap.replace_top(val);
|
|
|
|
ref.pop();
|
|
|
|
ref.push(val);
|
|
|
|
} else {
|
|
|
|
// pop
|
|
|
|
assert(size > 0);
|
|
|
|
heap.pop();
|
|
|
|
ref.pop();
|
|
|
|
--size;
|
|
|
|
}
|
|
|
|
|
|
|
|
// After every operation, check that the public methods give the same
|
|
|
|
// results
|
|
|
|
assert((size == 0) == ref.empty());
|
|
|
|
ASSERT_EQ(size == 0, heap.empty());
|
|
|
|
if (size > 0) {
|
|
|
|
ASSERT_EQ(ref.top(), heap.top());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Probabilities should be set up to occasionally hit the max heap size and
|
|
|
|
// drain it
|
|
|
|
assert(ndrains > 0);
|
|
|
|
|
|
|
|
heap.clear();
|
|
|
|
ASSERT_TRUE(heap.empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Basic test, MAX_VALUE = 3*MAX_HEAP_SIZE (occasional duplicates)
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
|
|
Basic, HeapTest,
|
|
|
|
::testing::Values(Params(1000, 3000, 0x1b575cf05b708945))
|
|
|
|
);
|
|
|
|
// Mid-size heap with small values (many duplicates)
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
|
|
SmallValues, HeapTest,
|
|
|
|
::testing::Values(Params(100, 10, 0x5ae213f7bd5dccd0))
|
|
|
|
);
|
|
|
|
// Small heap, large value range (no duplicates)
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
|
|
SmallHeap, HeapTest,
|
|
|
|
::testing::Values(Params(10, ULLONG_MAX, 0x3e1fa8f4d01707cf))
|
|
|
|
);
|
|
|
|
// Two-element heap
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
|
|
TwoElementHeap, HeapTest,
|
|
|
|
::testing::Values(Params(2, 5, 0x4b5e13ea988c6abc))
|
|
|
|
);
|
|
|
|
// One-element heap
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
|
|
OneElementHeap, HeapTest,
|
|
|
|
::testing::Values(Params(1, 3, 0x176a1019ab0b612e))
|
|
|
|
);
|
|
|
|
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
|
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
|
|
#ifdef GFLAGS
|
|
|
|
GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true);
|
|
|
|
#endif // GFLAGS
|
|
|
|
return RUN_ALL_TESTS();
|
|
|
|
}
|