Line data Source code
1 : /**
2 : *
3 : * @file FollyQueue_test.cxx FollyQueue class Unit Tests
4 : *
5 : * This is part of the DUNE DAQ Application Framework, copyright 2020.
6 : * Licensing/copyright details are in the COPYING file that you should have
7 : * received with this code.
8 : */
9 :
10 : #include "iomanager/queue/FollyQueue.hpp"
11 :
12 : #define BOOST_TEST_MODULE FollyQueue_test // NOLINT
13 : #include "boost/test/unit_test.hpp"
14 :
15 : #include <chrono>
16 : #include <utility>
17 :
18 : // For a first look at the code, you may want to skip past the
19 : // contents of the unnamed namespace and move ahead to the actual test
20 : // cases
21 :
22 : namespace {
23 :
24 : constexpr int max_testable_capacity = 1'000'000'000; ///< The maximum capacity this test will attempt to check
25 : constexpr double fractional_timeout_tolerance =
26 : 0.5; ///< The fraction of the timeout which the timing is allowed to be off by
27 :
28 : /**
29 : * @brief Timeout to use for tests
30 : *
31 : * Don't set the timeout to zero, otherwise the tests will fail since they'd
32 : * expect the push/pop functions to execute instananeously
33 : */
34 : constexpr auto timeout = std::chrono::milliseconds(5);
35 : /**
36 : * @brief Timeout expressed in microseconds
37 : */
38 : constexpr auto timeout_in_us = std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
39 :
40 : dunedaq::iomanager::FollySPSCQueue<int> queue("FollyQueue", 10); ///< Queue instance for the test
41 :
42 : } // namespace ""
43 :
44 : // This test case should run first. Make sure all other test cases depend on
45 : // this.
46 :
47 2 : BOOST_AUTO_TEST_CASE(sanity_checks)
48 : {
49 1 : BOOST_REQUIRE(!queue.can_pop());
50 :
51 1 : BOOST_REQUIRE_EQUAL(queue.get_capacity(), 10);
52 1 : BOOST_REQUIRE_EQUAL(queue.get_num_elements(), 0);
53 :
54 1 : auto start_time = std::chrono::steady_clock::now();
55 1 : try {
56 1 : BOOST_REQUIRE(queue.can_push());
57 1 : queue.push(42, timeout);
58 0 : } catch (const dunedaq::iomanager::QueueTimeoutExpired& ex) {
59 0 : BOOST_TEST_REQUIRE(false, "Test failure: unexpected timeout exception throw from push");
60 0 : } catch (...) { // NOLINT
61 0 : BOOST_TEST_REQUIRE(false, "Test failure: unexpected exception (non-timeout-related) thrown");
62 0 : }
63 :
64 1 : auto push_time = std::chrono::steady_clock::now() - start_time;
65 :
66 1 : if (push_time > timeout) {
67 0 : auto push_time_in_us = std::chrono::duration_cast<std::chrono::microseconds>(push_time).count();
68 :
69 0 : BOOST_TEST_REQUIRE(false,
70 : "Test failure: pushing element onto empty Queue "
71 : "resulted in a timeout (function exited after "
72 : << push_time_in_us << " microseconds, timeout is " << timeout_in_us << " microseconds)");
73 : }
74 :
75 1 : BOOST_REQUIRE(queue.can_pop());
76 1 : BOOST_REQUIRE_EQUAL(queue.get_num_elements(), 1);
77 :
78 1 : start_time = std::chrono::steady_clock::now();
79 1 : int popped_value = -999;
80 1 : try {
81 1 : queue.pop(popped_value, timeout);
82 0 : } catch (const dunedaq::iomanager::QueueTimeoutExpired& ex) {
83 0 : BOOST_TEST_REQUIRE(false, "Test failure: unexpected timeout exception throw from pop");
84 0 : } catch (...) { // NOLINT
85 0 : BOOST_TEST_REQUIRE(false, "Test failure: unexpected exception (non-timeout-related) thrown");
86 0 : }
87 :
88 1 : auto pop_time = std::chrono::steady_clock::now() - start_time;
89 :
90 1 : if (pop_time > timeout) {
91 0 : auto pop_time_in_us = std::chrono::duration_cast<std::chrono::microseconds>(pop_time).count();
92 0 : BOOST_TEST_REQUIRE(false,
93 : "Test failure: popping element off Queue "
94 : "resulted in a timeout without an exception throw (function exited after "
95 : << pop_time_in_us << " microseconds, timeout is " << timeout_in_us << " microseconds)");
96 : }
97 :
98 1 : BOOST_REQUIRE_EQUAL(popped_value, 42);
99 1 : }
100 :
101 2 : BOOST_AUTO_TEST_CASE(empty_checks, *boost::unit_test::depends_on("sanity_checks"))
102 : {
103 1 : int popped_value = -999;
104 :
105 1 : while (queue.can_pop()) {
106 :
107 0 : try {
108 0 : queue.pop(popped_value, timeout);
109 0 : } catch (const dunedaq::iomanager::QueueTimeoutExpired& ex) {
110 0 : BOOST_TEST(false,
111 : "Timeout exception thrown in call to FollyQueue::pop(); unable "
112 : "to empty the Queue");
113 0 : break;
114 0 : }
115 : }
116 :
117 1 : BOOST_REQUIRE(!queue.can_pop());
118 :
119 : // pop off of an empty Queue
120 :
121 1 : auto start_time = std::chrono::steady_clock::now();
122 2 : BOOST_CHECK_THROW(queue.pop(popped_value, timeout), dunedaq::iomanager::QueueTimeoutExpired);
123 1 : auto pop_duration = std::chrono::steady_clock::now() - start_time;
124 :
125 1 : const double fraction_of_pop_timeout_used =
126 1 : static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(pop_duration).count()) /
127 1 : std::chrono::duration_cast<std::chrono::nanoseconds>(timeout).count();
128 :
129 1 : BOOST_TEST_MESSAGE("Attempted pop_duration divided by timeout is " << fraction_of_pop_timeout_used);
130 :
131 1 : BOOST_CHECK_GT(fraction_of_pop_timeout_used, 1 - fractional_timeout_tolerance);
132 1 : BOOST_CHECK_LT(fraction_of_pop_timeout_used, 1 + fractional_timeout_tolerance);
133 1 : }
134 :
135 2 : BOOST_AUTO_TEST_CASE(full_checks, *boost::unit_test::depends_on("empty_checks"))
136 : {
137 1 : int push_value = 0;
138 :
139 11 : while (queue.can_push()) {
140 :
141 10 : try {
142 10 : int push_value_tmp = push_value;
143 10 : queue.push(std::move(push_value_tmp), timeout);
144 10 : push_value++;
145 0 : } catch (const dunedaq::iomanager::QueueTimeoutExpired& ex) {
146 0 : BOOST_TEST(false,
147 : "Timeout exception thrown in call to FollyQueue::push(); unable "
148 : "to fill the Queue");
149 0 : break;
150 0 : }
151 : }
152 :
153 1 : BOOST_REQUIRE(!queue.can_push());
154 1 : BOOST_REQUIRE_EQUAL(push_value, queue.get_capacity());
155 :
156 1 : int test_max_capacity = 1000000;
157 2 : while (push_value < test_max_capacity) {
158 : // push to a full Queue
159 2 : auto start_time = std::chrono::steady_clock::now();
160 2 : try {
161 2 : int push_value_tmp = push_value;
162 2 : queue.push(std::move(push_value_tmp), timeout);
163 1 : push_value++;
164 1 : } catch (dunedaq::iomanager::QueueTimeoutExpired&) {
165 1 : auto push_duration = std::chrono::steady_clock::now() - start_time;
166 1 : BOOST_TEST_MESSAGE("Timeout occurred. Capacity is " << queue.get_capacity() << ", current occupancy is "
167 : << queue.get_num_elements());
168 :
169 1 : const double fraction_of_push_timeout_used =
170 1 : static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(push_duration).count()) /
171 1 : std::chrono::duration_cast<std::chrono::nanoseconds>(timeout).count();
172 :
173 1 : BOOST_TEST_MESSAGE("Attempted push_duration divided by timeout is " << fraction_of_push_timeout_used);
174 :
175 1 : BOOST_CHECK_GT(fraction_of_push_timeout_used, 1 - fractional_timeout_tolerance);
176 1 : BOOST_CHECK_LT(fraction_of_push_timeout_used, 1 + fractional_timeout_tolerance);
177 1 : break;
178 1 : }
179 : }
180 1 : if (push_value == test_max_capacity) {
181 0 : BOOST_TEST_MESSAGE("Unable to cause push timeout in " << test_max_capacity << " pushes");
182 : }
183 1 : }
|