mysql5/mysql-5.7.27/unittest/gunit/bounded_queue-t.cc

559 lines
13 KiB
C++

/* Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
// First include (the generated) my_config.h, to get correct platform defines.
#include "my_config.h"
#include <gtest/gtest.h>
#include <algorithm>
#include <stddef.h>
#include "bounded_queue.h"
#include "fake_costmodel.h"
#include "filesort_utils.h"
#include "my_sys.h"
#include "opt_costmodel.h"
#include "test_utils.h"
#include "bounded_queue_c.h"
#include "bounded_queue_std.h"
#include "bounded_queue_boost.h"
#include "bounded_queue_boost.cc"
namespace bounded_queue_unittest {
const int num_elements= 14;
static int count_int_ptr_compare= 0;
static int count_operator= 0;
static int count_test_key= 0;
/*
Elements to be sorted by tests below.
We put some data in front of 'val' to verify (when debugging)
that all the reinterpret_casts involved when using QUEUE are correct.
*/
struct Test_element
{
Test_element() { *this= -1; }
Test_element(int i) { *this= i; }
Test_element &operator=(int i)
{
val= i;
my_snprintf(text, array_size(text), "%4d", i);
return *this;
}
char text[8]; // Some data.
int val; // The value we use for generating the key.
};
/*
The key, which is actually sorted by queue_xxx() functions.
We sort on the key only.
*/
struct Test_key
{
Test_key() : element(NULL), key(-1) {}
Test_element *element; // The actual data element.
int key; // The generated key for the data element.
};
/*
Comparison function for Test_key objects.
*/
int test_key_compare(size_t *cmp_arg, Test_key **a, Test_key **b)
{
EXPECT_EQ(*cmp_arg, sizeof(int));
int a_num= (*a)->key;
int b_num= (*b)->key;
if (a_num > b_num)
return +1;
if (a_num < b_num)
return -1;
return 0;
}
class Test_key_compare
{
public:
bool operator()(const Test_key *a, const Test_key *b) const
{
++count_test_key;
return a->key < b->key;
}
size_t m_size;
};
/*
Generates a Test_key for a given Test_element.
*/
class Test_keymaker
{
public:
uint make_sortkey(Test_key *key, Test_element *element)
{
key->element= element;
key->key= element->val;
return sizeof(key->key);
}
size_t compare_length() const { return sizeof(int); }
};
/*
A struct to wrap the actual keys, and an array of pointers to the keys.
*/
template<int sz, typename Key_type>
struct Key_container
{
Key_container()
{
for (int ix= 0; ix <= sz; ++ix)
key_ptrs[ix]= &key_data[ix];
}
Key_type *key_ptrs[sz+1];
Key_type key_data[sz+1];
};
class BoundedQueueTest : public ::testing::Test
{
protected:
BoundedQueueTest() {}
virtual void SetUp()
{
int ix;
for (ix=0; ix < array_size(m_test_data); ++ix)
m_test_data[ix]= ix;
std::random_shuffle(&m_test_data[0], &m_test_data[array_size(m_test_data)]);
}
void insert_test_data()
{
for (int ix= 0; ix < array_size(m_test_data); ++ix)
m_queue.push(&m_test_data[ix]);
}
void insert_test_data_heap()
{
for (int ix= 0; ix < array_size(m_test_data); ++ix)
{
m_heap.push(&m_test_data[ix]);
}
}
// Key pointers and data, used by the queue_xxx() functions.
Key_container<num_elements / 2, Test_key> m_keys;
// Some random intput data, to be sorted.
Test_element m_test_data[num_elements];
Test_keymaker m_keymaker;
Bounded_QUEUE<Test_element *, Test_key *, Test_keymaker> m_queue;
Bounded_queue<Test_element *, Test_key *,
Test_keymaker, Test_key_compare> m_heap;
private:
GTEST_DISALLOW_COPY_AND_ASSIGN_(BoundedQueueTest);
};
// Google Test recommends DeathTest suffix for classes used in death tests.
typedef BoundedQueueTest BoundedQueueDeathTest;
#if !defined(DBUG_OFF)
/*
Verifies that we DBUG_ASSERT if trying to push to an un-initialized queue.
*/
TEST_F(BoundedQueueDeathTest, DieIfNotInitialized)
{
::testing::FLAGS_gtest_death_test_style = "threadsafe";
Test_element foo= 1;
EXPECT_DEATH_IF_SUPPORTED(m_queue.push(&foo),
".*Assertion .*is_initialized.*");
}
/*
Verifies that popping an empty queue hits a DBUG_ASSERT.
*/
TEST_F(BoundedQueueDeathTest, DieIfPoppingEmptyQueue)
{
EXPECT_EQ(0, m_queue.init(0, true, test_key_compare,
&m_keymaker, m_keys.key_ptrs));
::testing::FLAGS_gtest_death_test_style = "threadsafe";
EXPECT_DEATH_IF_SUPPORTED(m_queue.pop(),
".*Assertion .*elements > 0.*");
}
#endif // !defined(DBUG_OFF)
/*
Verifies that construct, initialize, destroy works.
*/
TEST_F(BoundedQueueTest, ConstructAndDestruct)
{
EXPECT_EQ(0, m_queue.init(num_elements/2, true,
test_key_compare,
&m_keymaker, m_keys.key_ptrs));
}
/*
Verifies that we reject too large queues.
*/
TEST_F(BoundedQueueTest, TooManyElements)
{
EXPECT_EQ(1, m_queue.init(UINT_MAX, true,
test_key_compare,
&m_keymaker, m_keys.key_ptrs));
EXPECT_EQ(1, m_queue.init(UINT_MAX - 1, true,
test_key_compare,
&m_keymaker, m_keys.key_ptrs));
}
/*
Verifies that zero-size queue works.
*/
TEST_F(BoundedQueueTest, ZeroSizeQueue)
{
EXPECT_EQ(0, m_queue.init(0, true, test_key_compare,
&m_keymaker, m_keys.key_ptrs));
insert_test_data();
// There is always one extra element in the queue.
EXPECT_EQ(1U, m_queue.num_elements());
}
/*
Verifies that push and bounded size works, and that pop() gives sorted order.
*/
TEST_F(BoundedQueueTest, PushAndPopKeepLargest)
{
EXPECT_EQ(0, m_queue.init(num_elements/2, false, test_key_compare,
&m_keymaker, m_keys.key_ptrs));
insert_test_data();
// We expect the queue to contain [7 .. 13]
const int max_key_val= array_size(m_test_data) - 1;
EXPECT_EQ(static_cast<uint>(num_elements / 2 + 1), m_queue.num_elements());
while (m_queue.num_elements() > 0)
{
Test_key **top= m_queue.pop();
int expected_key_val= max_key_val - m_queue.num_elements();
int key_val= (*top)->key;
EXPECT_EQ(expected_key_val, key_val);
Test_element *element= (*top)->element;
EXPECT_EQ(expected_key_val, element->val);
}
}
/*
Verifies that push and bounded size works, and that pop() gives sorted order.
Note that with max_at_top == true, we will pop() in reverse order.
*/
TEST_F(BoundedQueueTest, PushAndPopKeepSmallest)
{
EXPECT_EQ(0, m_queue.init(num_elements/2, true, test_key_compare,
&m_keymaker, m_keys.key_ptrs));
insert_test_data();
// We expect the queue to contain [6 .. 0]
while (m_queue.num_elements() > 0)
{
Test_key **top= m_queue.pop();
int expected_key_val= m_queue.num_elements();
int key_val= (*top)->key;
EXPECT_EQ(expected_key_val, key_val);
Test_element *element= (*top)->element;
EXPECT_EQ(expected_key_val, element->val);
}
}
static void my_string_ptr_sort(uchar *base, uint items, size_t size)
{
#if INT_MAX > 65536L
uchar **ptr=0;
if (radixsort_is_appliccable(items, size) &&
(ptr= (uchar**) my_malloc(PSI_NOT_INSTRUMENTED,
items*sizeof(char*),MYF(0))))
{
radixsort_for_str_ptr((uchar**) base,items,size,ptr);
my_free(ptr);
}
else
#endif
{
if (size && items)
{
my_qsort2(base,items, sizeof(uchar*), my_testing::get_ptr_compare(size),
(void*) &size);
}
}
}
/*
Verifies that push, with bounded size, followed by sort() works.
*/
TEST_F(BoundedQueueTest, InsertAndSort)
{
EXPECT_EQ(0, m_queue.init(num_elements/2, true, test_key_compare,
&m_keymaker, m_keys.key_ptrs));
insert_test_data();
uchar *base= (uchar*) &m_keys.key_ptrs[0];
size_t size= sizeof(Test_key);
// We sort our keys as strings, so erase all the element pointers first.
for (int ii= 0; ii < array_size(m_keys.key_data); ++ii)
m_keys.key_data[ii].element= NULL;
my_string_ptr_sort(base, array_size(m_keys.key_ptrs), size);
for (int ii= 0; ii < num_elements/2; ++ii)
{
Test_key *sorted_key= m_keys.key_ptrs[ii];
EXPECT_EQ(ii, sorted_key->key);
}
}
/*
A test of the function get_merge_many_buffs_cost_fast()
*/
TEST(CostEstimationTest, MergeManyBuff)
{
ha_rows num_rows= 512;
ulong num_keys= 100;
ulong row_lenght= 100;
double prev_cost= 0.0;
// Make a cost model object that the merge code will use
Fake_Cost_model_table cost_model_table;
while (num_rows <= MAX_FILE_SIZE/4)
{
const double merge_cost=
get_merge_many_buffs_cost_fast(num_rows, num_keys, row_lenght,
&cost_model_table);
EXPECT_LT(0.0, merge_cost);
EXPECT_LT(prev_cost, merge_cost);
num_rows*= 2;
prev_cost= merge_cost;
}
}
/*
Comparison function for integers.
*/
int int_ptr_compare(size_t *cmp_arg, int **a, int **b)
{
EXPECT_EQ(*cmp_arg, sizeof(int));
++count_int_ptr_compare;
int a_num= **a;
int b_num= **b;
if (a_num > b_num)
return +1;
if (a_num < b_num)
return -1;
return 0;
}
class Int_ptr_compare
{
public:
bool operator()(const int *a, const int *b) const
{
++count_operator;
return *a > *b;
}
size_t m_compare_length;
};
/*
Generates an integer key for a given integer element.
*/
class Int_keymaker
{
public:
uint make_sortkey(int *to, int *from)
{
memcpy(to, from, sizeof(int));
return sizeof(int);
}
size_t compare_length() const { return sizeof(int); }
};
/*
Some basic performance testing, to compute the overhead of Bounded_QUEUE.
Run the with 'bounded_queue-t --disable-tap-output' to see the
millisecond output from Google Test.
./bounded_queue-t --disable-tap-output --gtest_filter="PerfTest*"
For testing, I used
num_rows = 100000
row_limit = { 1, 10, 100, 1000, 10000 }
num_iterations = 100
The inser_into_xxx() functions insert rand() data.
Also test with
identical data : queue.push(0)
increasing keys: queue.push(ix)
decreasing keys: queue.push(-ix)
*/
const ha_rows num_rows= 100000;
const ha_rows row_limit= 1;
const int num_iterations= 1;
inline int test_data(int ix MY_ATTRIBUTE((unused)))
{
return rand();
// return 42;
// return ix;
// return -ix;
}
class PerfTest : public ::testing::Test
{
public:
virtual void SetUp()
{
count_int_ptr_compare= 0;
count_operator= 0;
count_test_key= 0;
}
virtual void TearDown()
{
std::cout << "C-compare " << count_int_ptr_compare
<< " Cpp-compare " << count_operator << std::endl << std::endl;
}
/*
The extra overhead of malloc/free should be part of the measurement,
so we do not define the key container as a member here.
*/
typedef Key_container<row_limit, int> Container;
};
// Different queue.init, so separate insert function, see insert_into_heap.
void insert_into_queue()
{
typedef Key_container<row_limit, int> Container;
std::cout << "insert " << num_rows
<< " rows into queue size " << row_limit << std::endl;
for (int it= 0; it < num_iterations; ++it)
{
Container *keys= new Container;
srand(0);
Int_keymaker int_keymaker;
Bounded_QUEUE<int *, int *, Int_keymaker> queue;
EXPECT_EQ(0, queue.init(row_limit, false, int_ptr_compare,
&int_keymaker, keys->key_ptrs));
for (ha_rows ix= 0; ix < num_rows; ++ix)
{
int data= test_data(ix);
queue.push(&data);
}
delete keys;
}
}
// Inserts test data into Queue.
template<typename Queue_t>
void insert_into_heap()
{
typedef Key_container<row_limit, int> Container;
std::cout << "insert " << num_rows
<< " rows into heap size " << row_limit << std::endl;
for (int it= 0; it < num_iterations; ++it)
{
Container *keys= new Container;
srand(0);
Int_keymaker int_keymaker;
Queue_t queue;
EXPECT_EQ(0, queue.init(row_limit,
&int_keymaker, keys->key_ptrs));
for (ha_rows ix= 0; ix < num_rows; ++ix)
{
int data= test_data(ix);
queue.push(&data);
}
delete keys;
}
}
TEST_F(PerfTest, InsertIntoQUEUE)
{
insert_into_queue();
}
TEST_F(PerfTest, InsertIntoPriorityQueue)
{
insert_into_heap<Bounded_queue<int *, int *,
Int_keymaker, Int_ptr_compare> >();
}
TEST_F(PerfTest, InsertIntoStdQueue)
{
insert_into_heap<Bounded_queue_std<int *, int *,
Int_keymaker, Int_ptr_compare> >();
}
TEST_F(PerfTest, InsertIntoBoostQueue)
{
insert_into_heap<Bounded_queue_boost<int *, int *,
Int_keymaker, Int_ptr_compare> >();
}
/*
Computes the overhead of setting up sort arrays, and rand() calls.
*/
TEST_F(PerfTest, NoSorting)
{
for (int it= 0; it < num_iterations; ++it)
{
Container *keys= new Container;
srand(0);
for (ha_rows ix= 0; ix < row_limit; ++ix)
{
int data= rand();
keys->key_data[ix]= data;
}
delete keys;
}
}
} // namespace