/* * Unit tests for data structures. * * Depends on the Boost.Test unit test framework. * Tested with Boost v1.74, available from https://www.boost.org/ */ #include #include #include #include #include #include #include #include #include #define BOOST_TEST_MODULE datastruct #include #include "datastruct.h" /* ********** Test ConcatenableQueue ********** */ BOOST_AUTO_TEST_SUITE(test_concatenable_queue) BOOST_AUTO_TEST_CASE(test_single) { using Queue = ConcatenableQueue; Queue q("Q"); BOOST_TEST(q.name() == std::string("Q")); Queue::Node n; q.insert(&n, 4, "a"); BOOST_TEST(n.find() == std::string("Q")); BOOST_TEST(n.prio() == 4); BOOST_TEST(q.min_prio() == 4); BOOST_TEST(q.min_elem() == std::string("a")); n.set_prio(8); BOOST_TEST(n.prio() == 8); BOOST_TEST(n.find() == std::string("Q")); BOOST_TEST(q.min_prio() == 8); BOOST_TEST(q.min_elem() == std::string("a")); } BOOST_AUTO_TEST_CASE(test_simple) { using Queue = ConcatenableQueue; Queue::Node n1, n2, n3, n4, n5; Queue q1("A"); q1.insert(&n1, 5, 'a'); Queue q2("B"); q2.insert(&n2, 6, 'b'); Queue q3("C"); q3.insert(&n3, 7, 'c'); Queue q4("D"); q4.insert(&n4, 4, 'd'); Queue q5("E"); q5.insert(&n5, 3, 'e'); auto m345 = {&q3, &q4, &q5}; Queue q345("P"); q345.merge(m345.begin(), m345.end()); BOOST_TEST(n1.find() == std::string("A")); BOOST_TEST(n2.find() == std::string("B")); BOOST_TEST(n3.find() == std::string("P")); BOOST_TEST(n4.find() == std::string("P")); BOOST_TEST(n5.find() == std::string("P")); BOOST_TEST(q345.min_prio() == 3); BOOST_TEST(q345.min_elem() == 'e'); n5.set_prio(6); BOOST_TEST(q345.min_prio() == 4); BOOST_TEST(q345.min_elem() == 'd'); auto m12 = {&q1, &q2}; Queue q12("Q"); q12.merge(m12.begin(), m12.end()); BOOST_TEST(n1.find() == std::string("Q")); BOOST_TEST(n2.find() == std::string("Q")); BOOST_TEST(q12.min_prio() == 5); BOOST_TEST(q12.min_elem() == 'a'); auto m12345 = {&q12, &q345}; Queue q12345("R"); q12345.merge(m12345.begin(), m12345.end()); BOOST_TEST(n1.find() == std::string("R")); BOOST_TEST(n2.find() == std::string("R")); BOOST_TEST(n3.find() == std::string("R")); BOOST_TEST(n4.find() == std::string("R")); BOOST_TEST(n5.find() == std::string("R")); BOOST_TEST(q12345.min_prio() == 4); BOOST_TEST(q12345.min_elem() == 'd'); n4.set_prio(8); BOOST_TEST(q12345.min_prio() == 5); BOOST_TEST(q12345.min_elem() == 'a'); n3.set_prio(2); BOOST_TEST(q12345.min_prio() == 2); BOOST_TEST(q12345.min_elem() == 'c'); q12345.split(); BOOST_TEST(n1.find() == std::string("Q")); BOOST_TEST(n2.find() == std::string("Q")); BOOST_TEST(n3.find() == std::string("P")); BOOST_TEST(n4.find() == std::string("P")); BOOST_TEST(n5.find() == std::string("P")); BOOST_TEST(q12.min_prio() == 5); BOOST_TEST(q12.min_elem() == 'a'); BOOST_TEST(q345.min_prio() == 2); BOOST_TEST(q345.min_elem() == 'c'); q12.split(); BOOST_TEST(n1.find() == std::string("A")); BOOST_TEST(n2.find() == std::string("B")); q345.split(); BOOST_TEST(n3.find() == std::string("C")); BOOST_TEST(n4.find() == std::string("D")); BOOST_TEST(n5.find() == std::string("E")); BOOST_TEST(q3.min_prio() == 2); BOOST_TEST(q3.min_elem() == 'c'); } BOOST_AUTO_TEST_CASE(test_medium) { using Queue = ConcatenableQueue; std::vector queue_names(19); for (int i = 0; i < 14; i++) { queue_names[i] = 'A' + i; } queue_names[14] = 'P'; queue_names[15] = 'Q'; queue_names[16] = 'R'; queue_names[17] = 'S'; queue_names[18] = 'Z'; std::vector queues(queue_names.begin(), queue_names.end()); Queue::Node nodes[14]; int prios[14] = {3, 8, 6, 2, 9, 4, 6, 8, 1, 5, 9, 4, 7, 8}; auto min_prio = [&prios](int begin, int end) -> int { int p = begin; int m = prios[p]; ++p; while (p != end) { m = std::min(m, prios[p]); ++p; } return m; }; for (int i = 0; i < 14; i++) { BOOST_TEST(queues[i].name() == 'A' + i); queues[i].insert(&nodes[i], prios[i], 'a' + i); } auto m14 = {&queues[0], &queues[1]}; queues[14].merge(m14.begin(), m14.end()); BOOST_TEST(queues[14].name() == 'P'); BOOST_TEST(queues[14].min_prio() == min_prio(0, 2)); auto m15 = {&queues[2], &queues[3], &queues[4]}; queues[15].merge(m15.begin(), m15.end()); BOOST_TEST(queues[15].name() == 'Q'); BOOST_TEST(queues[15].min_prio() == min_prio(2, 5)); auto m16 = {&queues[5], &queues[6], &queues[7], &queues[8]}; queues[16].merge(m16.begin(), m16.end()); BOOST_TEST(queues[16].name() == 'R'); BOOST_TEST(queues[16].min_prio() == min_prio(5, 9)); auto m17 = {&queues[9], &queues[10], &queues[11], &queues[12], &queues[13]}; queues[17].merge(m17.begin(), m17.end()); BOOST_TEST(queues[17].name() == 'S'); BOOST_TEST(queues[17].min_prio() == min_prio(9, 14)); for (int i = 0; i < 2; i++) { BOOST_TEST(nodes[i].find() == 'P'); } for (int i = 2; i < 5; i++) { BOOST_TEST(nodes[i].find() == 'Q'); } for (int i = 5; i < 9; i++) { BOOST_TEST(nodes[i].find() == 'R'); } for (int i = 9; i < 14; i++) { BOOST_TEST(nodes[i].find() == 'S'); } auto m18 = {&queues[14], &queues[15], &queues[16], &queues[17]}; queues[18].merge(m18.begin(), m18.end()); BOOST_TEST(queues[18].name() == 'Z'); BOOST_TEST(queues[18].min_prio() == 1); BOOST_TEST(queues[18].min_elem() == 'i'); for (int i = 0; i < 14; i++) { BOOST_TEST(nodes[i].find() == 'Z'); } prios[8] = 5; nodes[8].set_prio(prios[8]); BOOST_TEST(queues[18].min_prio() == 2); BOOST_TEST(queues[18].min_elem() == 'd'); queues[18].split(); for (int i = 0; i < 2; i++) { BOOST_TEST(nodes[i].find() == 'P'); } for (int i = 2; i < 5; i++) { BOOST_TEST(nodes[i].find() == 'Q'); } for (int i = 5; i < 9; i++) { BOOST_TEST(nodes[i].find() == 'R'); } for (int i = 9; i < 14; i++) { BOOST_TEST(nodes[i].find() == 'S'); } BOOST_TEST(queues[14].min_prio() == min_prio(0, 2)); BOOST_TEST(queues[15].min_prio() == min_prio(2, 5)); BOOST_TEST(queues[16].min_prio() == min_prio(5, 9)); BOOST_TEST(queues[17].min_prio() == min_prio(9, 14)); for (int i = 14; i < 18; i++) { queues[i].split(); } for (int i = 0; i < 14; i++) { BOOST_TEST(nodes[i].find() == 'A' + i); BOOST_TEST(queues[i].min_prio() == prios[i]); BOOST_TEST(queues[i].min_elem() == 'a' + i); } } BOOST_AUTO_TEST_CASE(test_random) { using Queue = ConcatenableQueue; constexpr int NUM_NODES = 4000; std::mt19937 rng(23456); std::uniform_real_distribution<> prio_distribution; std::uniform_int_distribution<> node_distribution(0, NUM_NODES - 1); double prios[NUM_NODES]; Queue::Node nodes[NUM_NODES]; std::unordered_map> queues; std::unordered_map> queue_nodes; std::unordered_map> queue_subs; std::set live_queues; std::set live_merged_queues; // Make trivial queues. for (int i = 0; i < NUM_NODES; i++) { int name = 10000 + i; prios[i] = prio_distribution(rng); queues[name] = std::unique_ptr(new Queue(name)); queues[name]->insert(&nodes[i], prios[i], i); queue_nodes[name].insert(i); live_queues.insert(name); } // Run modifications. for (int i = 0; i < 4000; i++) { // Find top-level queue of few nodes. for (int k = 0; k < 200; k++) { int t = node_distribution(rng); int name = nodes[t].find(); BOOST_TEST(queue_nodes[name].count(t) == 1); } // Change priority of a few nodes. for (int k = 0; k < 10; k++) { int t = node_distribution(rng); int name = nodes[t].find(); BOOST_TEST(live_queues.count(name) == 1); BOOST_TEST(queue_nodes[name].count(t) == 1); BOOST_TEST(nodes[t].prio() == prios[t]); double p = prio_distribution(rng); prios[t] = p; nodes[t].set_prio(p); for (int tt : queue_nodes[name]) { if (prios[tt] < p) { t = tt; p = prios[tt]; } } BOOST_TEST(queues[name]->min_prio() == p); BOOST_TEST(queues[name]->min_elem() == t); } if (live_queues.size() > 100) { // Choose number of queues to merge between 2 and 100. int k = std::uniform_int_distribution<>(2, 100)(rng); k = std::uniform_int_distribution<>(2, k)(rng); // Choose queues to merge. std::vector live_queue_vec(live_queues.begin(), live_queues.end()); std::vector sub_names; std::vector sub_queues; for (int ki = 0; ki < k; ki++) { int t = std::uniform_int_distribution<>( 0, live_queue_vec.size() - 1)(rng); int name = live_queue_vec[t]; sub_names.push_back(name); sub_queues.push_back(queues[name].get()); live_queue_vec[t] = live_queue_vec.back(); live_queue_vec.pop_back(); live_queues.erase(name); live_merged_queues.erase(name); } // Create new queue by merging selected queues. int name = 20000 + i; queues[name] = std::unique_ptr(new Queue(name)); queues[name]->merge(sub_queues.begin(), sub_queues.end()); for (int nn : sub_names) { queue_nodes[name].insert(queue_nodes[nn].begin(), queue_nodes[nn].end()); } queue_subs[name].insert(sub_names.begin(), sub_names.end()); live_queues.insert(name); live_merged_queues.insert(name); // Check new queue. { double p = 2; int t = 0; for (int tt : queue_nodes[name]) { if (prios[tt] < p) { t = tt; p = prios[tt]; } } BOOST_TEST(queues[name]->min_prio() == p); BOOST_TEST(queues[name]->min_elem() == t); } } if ((live_queues.size() <= 100) || (live_merged_queues.size() >= 100)) { // Choose a random queue to split. std::vector live_queue_vec(live_merged_queues.begin(), live_merged_queues.end()); int k = std::uniform_int_distribution<>( 0, live_queue_vec.size() - 1)(rng); int name = live_queue_vec[k]; queues[name]->split(); for (int nn : queue_subs[name]) { // Check reconstructed sub-queue. double p = 2; int t = 0; for (int tt : queue_nodes[nn]) { if (prios[tt] < p) { t = tt; p = prios[tt]; } } BOOST_TEST(queues[nn]->min_prio() == p); BOOST_TEST(queues[nn]->min_elem() == t); // Mark sub-queue as live. live_queues.insert(nn); if (queue_subs.count(nn) == 1) { live_merged_queues.insert(nn); } } live_merged_queues.erase(name); live_queues.erase(name); queues.erase(name); queue_nodes.erase(name); queue_subs.erase(name); } } } BOOST_AUTO_TEST_SUITE_END() /* ********** Test PriorityQueue ********** */ BOOST_AUTO_TEST_SUITE(test_priority_queue) BOOST_AUTO_TEST_CASE(test_empty) { using Queue = PriorityQueue; Queue q; BOOST_TEST(q.empty() == true); } BOOST_AUTO_TEST_CASE(test_single) { using Queue = PriorityQueue; Queue q; Queue::Node n1; BOOST_TEST(n1.valid() == false); q.insert(&n1, 5, "a"); BOOST_TEST(n1.valid() == true); BOOST_TEST(n1.prio() == 5); BOOST_TEST(q.empty() == false); BOOST_TEST(q.min_prio() == 5); BOOST_TEST(q.min_elem() == std::string("a")); q.set_prio(&n1, 3); BOOST_TEST(n1.prio() == 3); BOOST_TEST(q.min_prio() == 3); BOOST_TEST(q.min_elem() == std::string("a")); q.remove(&n1); BOOST_TEST(n1.valid() == false); BOOST_TEST(q.empty() == true); } BOOST_AUTO_TEST_CASE(test_simple) { using Queue = PriorityQueue; Queue q; Queue::Node nodes[10]; q.insert(&nodes[0], 9, 'a'); BOOST_TEST(q.min_prio() == 9); BOOST_TEST(q.min_elem() == 'a'); q.insert(&nodes[1], 4, 'b'); BOOST_TEST(q.min_prio() == 4); BOOST_TEST(q.min_elem() == 'b'); q.insert(&nodes[2], 7, 'c'); BOOST_TEST(q.min_prio() == 4); BOOST_TEST(q.min_elem() == 'b'); q.insert(&nodes[3], 5, 'd'); BOOST_TEST(q.min_prio() == 4); BOOST_TEST(q.min_elem() == 'b'); q.insert(&nodes[4], 8, 'e'); BOOST_TEST(q.min_prio() == 4); BOOST_TEST(q.min_elem() == 'b'); q.insert(&nodes[5], 6, 'f'); BOOST_TEST(q.min_prio() == 4); BOOST_TEST(q.min_elem() == 'b'); q.insert(&nodes[6], 4, 'g'); q.insert(&nodes[7], 5, 'h'); q.insert(&nodes[8], 2, 'i'); BOOST_TEST(q.min_prio() == 2); BOOST_TEST(q.min_elem() == 'i'); q.insert(&nodes[9], 6, 'j'); BOOST_TEST(q.min_prio() == 2); BOOST_TEST(q.min_elem() == 'i'); q.set_prio(&nodes[2], 1); BOOST_TEST(q.min_prio() == 1); BOOST_TEST(q.min_elem() == 'c'); q.set_prio(&nodes[4], 3); BOOST_TEST(q.min_prio() == 1); BOOST_TEST(q.min_elem() == 'c'); q.remove(&nodes[2]); BOOST_TEST(q.min_prio() == 2); BOOST_TEST(q.min_elem() == 'i'); q.remove(&nodes[8]); BOOST_TEST(q.min_prio() == 3); BOOST_TEST(q.min_elem() == 'e'); q.remove(&nodes[4]); q.remove(&nodes[1]); BOOST_TEST(q.min_prio() == 4); BOOST_TEST(q.min_elem() == 'g'); q.remove(&nodes[3]); q.remove(&nodes[9]); BOOST_TEST(q.min_prio() == 4); BOOST_TEST(q.min_elem() == 'g'); q.remove(&nodes[6]); BOOST_TEST(q.min_prio() == 5); BOOST_TEST(q.min_elem() == 'h'); BOOST_TEST(q.empty() == false); q.clear(); BOOST_TEST(q.empty() == true); } BOOST_AUTO_TEST_CASE(test_increase_prio) { using Queue = PriorityQueue; Queue q; Queue::Node n1; q.insert(&n1, 5, 'a'); q.set_prio(&n1, 8); BOOST_TEST(n1.prio() == 8); BOOST_TEST(q.min_prio() == 8); q.clear(); Queue::Node n2, n3, n4; q.insert(&n1, 9, 'A'); q.insert(&n2, 4, 'b'); q.insert(&n3, 7, 'c'); q.insert(&n4, 5, 'd'); BOOST_TEST(q.min_prio() == 4); BOOST_TEST(q.min_elem() == 'b'); q.set_prio(&n2, 8); BOOST_TEST(n2.prio() == 8); BOOST_TEST(q.min_elem() == 'd'); BOOST_TEST(q.min_prio() == 5); q.set_prio(&n3, 10); BOOST_TEST(n3.prio() == 10); BOOST_TEST(q.min_elem() == 'd'); q.remove(&n4); BOOST_TEST(q.min_elem() == 'b'); q.remove(&n2); BOOST_TEST(q.min_prio() == 9); BOOST_TEST(q.min_elem() == 'A'); q.remove(&n1); BOOST_TEST(q.min_elem() == 'c'); BOOST_TEST(q.min_prio() == 10); q.remove(&n3); BOOST_TEST(q.empty() == true); } BOOST_AUTO_TEST_CASE(test_random) { using Queue = PriorityQueue; Queue q; const int num_elem = 1000; std::vector, int, int>> elems; int next_data = 0; std::mt19937 rng(34567); auto check = [&q,&elems]() { int min_prio = q.min_prio(); int min_data = q.min_elem(); int best_prio = INT_MAX; bool found = false; for (const auto& v : elems) { int this_prio = std::get<1>(v); int this_data = std::get<2>(v); best_prio = std::min(best_prio, this_prio); if ((this_prio == min_prio) && (this_data == min_data)) { found = true; } } BOOST_TEST(found == true); BOOST_TEST(min_prio == best_prio); }; for (int i = 0; i < num_elem; ++i) { ++next_data; int prio = std::uniform_int_distribution<>(0, 1000000)(rng); std::unique_ptr nptr(new Queue::Node); q.insert(nptr.get(), prio, next_data); elems.push_back(std::make_tuple(std::move(nptr), prio, next_data)); check(); } for (int i = 0; i < 10000; ++i) { int p = std::uniform_int_distribution<>(0, num_elem - 1)(rng); Queue::Node* node = std::get<0>(elems[p]).get(); int prio = std::get<1>(elems[p]); prio = std::uniform_int_distribution<>(0, 1000000)(rng); q.set_prio(node, prio); std::get<1>(elems[p]) = prio; check(); p = std::uniform_int_distribution<>(0, num_elem - 1)(rng); node = std::get<0>(elems[p]).get(); q.remove(node); elems.erase(elems.begin() + p); check(); ++next_data; prio = std::uniform_int_distribution<>(0, 1000000)(rng); std::unique_ptr nptr(new Queue::Node); q.insert(nptr.get(), prio, next_data); elems.push_back(std::make_tuple(std::move(nptr), prio, next_data)); check(); } for (int i = 0; i < num_elem; ++i) { int p = std::uniform_int_distribution<>(0, num_elem - 1 - i)(rng); Queue::Node* node = std::get<0>(elems[p]).get(); q.remove(node); elems.erase(elems.begin() + p); if (! elems.empty()) { check(); } } BOOST_TEST(q.empty() == true); } BOOST_AUTO_TEST_SUITE_END()