JayBeams  0.1
Another project to have fun coding.
ut_mold_udp_pacer.cpp
Go to the documentation of this file.
1 #include <jb/gmock/init.hpp>
4 
5 #include <boost/array.hpp>
6 #include <boost/asio.hpp>
7 #include <boost/test/unit_test.hpp>
8 
9 /**
10  * Helper types and functions to test jb::itch5::mold_udp_pacer
11  */
12 namespace {
13 /**
14  * A simple class to mock a suitable sink for jb::itch5::mold_udp_pacer.
15  */
16 struct mock_sink {
17  std::vector<std::vector<char>> packets;
18 
19  template <typename const_buffer_sequence>
20  void operator()(const_buffer_sequence const& buffers) {
21  std::vector<char> packet(boost::asio::buffer_size(buffers));
22  buffer_copy(boost::asio::buffer(packet), buffers);
23  packets.push_back(packet);
24  }
25 };
26 
27 struct mock_clock_interface {
29  MOCK_METHOD0(now, time_point());
30 
31  static mock_clock_interface& instance() {
32  static mock_clock_interface ins;
33  return ins;
34  }
35  static void clear() {
36  ::testing::Mock::VerifyAndClearExpectations(&instance());
37  }
38 };
39 
40 struct mock_clock : public std::chrono::steady_clock {
41  static time_point now() {
42  return mock_clock_interface::instance().now();
43  }
44 };
45 } // anonymous namespace
46 
47 /**
48  * @test Verify that jb::itch5::mold_udp_pacer works as expected for a
49  * simple stream of messages.
50  */
51 BOOST_AUTO_TEST_CASE(itch5_mold_udp_pacer_basic) {
52  auto mock_sleep = [](mock_clock::duration const&) {};
53  mock_sink sink;
54  using namespace ::testing;
55  mock_clock_interface::clear();
56  EXPECT_CALL(mock_clock_interface::instance(), now())
57  .WillRepeatedly(Invoke([]() {
58  static int ts = 0;
59  return mock_clock::time_point(std::chrono::microseconds(++ts));
60  }));
61 
64 
65  /// Send 3 messages every 10 usecs, of different sizes and types
67  'A', jb::itch5::timestamp{std::chrono::microseconds(5)}, 100);
69  mock_clock::now(), jb::itch5::unknown_message(0, 0, m1.size(), &m1[0]),
70  sink, mock_sleep);
71 
73  'B', jb::itch5::timestamp{std::chrono::microseconds(15)}, 90);
75  mock_clock::now(), jb::itch5::unknown_message(1, 0, m2.size(), &m2[0]),
76  sink, mock_sleep);
77 
79  'A', jb::itch5::timestamp{std::chrono::microseconds(25)}, 80);
81  mock_clock::now(), jb::itch5::unknown_message(2, 0, m3.size(), &m3[0]),
82  sink, mock_sleep);
83 
84  p.heartbeat(sink);
85 
87  BOOST_REQUIRE_EQUAL(sink.packets.size(), 3);
88  BOOST_CHECK_EQUAL(100 + 2 + hdrsize, sink.packets.at(0).size());
89  BOOST_CHECK_EQUAL(90 + 2 + hdrsize, sink.packets.at(1).size());
90  BOOST_CHECK_EQUAL(80 + 2 + hdrsize, sink.packets.at(2).size());
91 }
92 
93 /**
94  * @test Verify that multiple back-to-back messages are grouped into a
95  * single packet.
96  */
97 BOOST_AUTO_TEST_CASE(itch5_mold_udp_pacer_coalesce) {
98  // create all the mock objects ...
99  auto mock_sleep = [](mock_clock::duration const&) {};
100  using namespace ::testing;
101  mock_clock_interface::clear();
102  EXPECT_CALL(mock_clock_interface::instance(), now())
103  .WillRepeatedly(Invoke([]() {
104  static int ts = 0;
105  return mock_clock::time_point(std::chrono::microseconds(++ts));
106  }));
107  mock_sink sink;
108 
109  // ... create a pacer that commits up to 1024 bytes and blocks for
110  // up to a second ...
115 
116  using namespace std::chrono;
117  // simulate 3 messages every 10 usecs, of different sizes and types ...
118  int msgcnt = 0;
120  'A', jb::itch5::timestamp{std::chrono::microseconds(5)}, 100);
121  p.handle_message(
122  mock_clock::now(),
123  jb::itch5::unknown_message(msgcnt++, 0, m1.size(), &m1[0]), sink,
124  mock_sleep);
125 
127  'B', jb::itch5::timestamp{std::chrono::microseconds(15)}, 90);
128  p.handle_message(
129  mock_clock::now(),
130  jb::itch5::unknown_message(msgcnt++, 0, m2.size(), &m2[0]), sink,
131  mock_sleep);
132 
134  'A', jb::itch5::timestamp{std::chrono::microseconds(25)}, 80);
135  p.handle_message(
136  mock_clock::now(),
137  jb::itch5::unknown_message(msgcnt++, 0, m3.size(), &m3[0]), sink,
138  mock_sleep);
139 
140  // ... we expect that no messages have been sent so far ...
141  BOOST_CHECK_EQUAL(sink.packets.size(), 0);
142 
143  // ... we force a flush ...
144  p.heartbeat(sink);
145 
147  // ... we should receive a single packet with all 3 messages ...
148  BOOST_REQUIRE_EQUAL(sink.packets.size(), 1);
149  BOOST_CHECK_EQUAL(
150  hdrsize + 100 + 2 + 90 + 2 + 80 + 2, sink.packets.at(0).size());
151 }
152 
153 /**
154  * @test Verify that multiple back-to-back messages are flushed if the
155  * packet is about to get full...
156  */
157 BOOST_AUTO_TEST_CASE(itch5_mold_udp_pacer_flush_full) {
158  // create all the mock objects ...
159  auto mock_sleep = [](mock_clock::duration const&) {};
160  using namespace ::testing;
161  mock_clock_interface::clear();
162  EXPECT_CALL(mock_clock_interface::instance(), now())
163  .WillRepeatedly(Invoke([]() {
164  static int ts = 0;
165  return mock_clock::time_point(std::chrono::microseconds(++ts));
166  }));
167  mock_sink sink;
168 
169  // ... create a pacer that commits up to 220 bytes and blocks for
170  // up to a second ...
175 
176  using namespace std::chrono;
177  // simulate 3 messages every 10 usecs, of different sizes and types ...
178  int msgcnt = 0;
180  'A', jb::itch5::timestamp{std::chrono::microseconds(5)}, 100);
181  p.handle_message(
182  mock_clock::now(),
183  jb::itch5::unknown_message(msgcnt++, 0, m1.size(), &m1[0]), sink,
184  mock_sleep);
185 
187  'B', jb::itch5::timestamp{std::chrono::microseconds(15)}, 90);
188  p.handle_message(
189  mock_clock::now(),
190  jb::itch5::unknown_message(msgcnt++, 0, m2.size(), &m2[0]), sink,
191  mock_sleep);
192 
193  // ... we expect that no messages have been sent so far ...
194  BOOST_CHECK_EQUAL(sink.packets.size(), 0);
195 
197  'A', jb::itch5::timestamp{std::chrono::microseconds(25)}, 80);
198  p.handle_message(
199  mock_clock::now(),
200  jb::itch5::unknown_message(msgcnt++, 0, m3.size(), &m3[0]), sink,
201  mock_sleep);
202 
204  // ... we should receive a single packet with the first 2 messages ...
205  BOOST_REQUIRE_EQUAL(sink.packets.size(), 1);
206  BOOST_CHECK_EQUAL(hdrsize + 100 + 2 + 90 + 2, sink.packets.at(0).size());
207 
208  // ... create a heartbeat, that should flush the last message ...
209  p.heartbeat(sink);
210  BOOST_REQUIRE_EQUAL(sink.packets.size(), 2);
211  BOOST_CHECK_EQUAL(hdrsize + 80 + 2, sink.packets.at(1).size());
212 }
213 
214 /**
215  * @test Verify that multiple back-to-back messages are flushed if the
216  * packet is about to get full...
217  */
218 BOOST_AUTO_TEST_CASE(itch5_mold_udp_pacer_flush_timeout) {
219  using namespace ::testing;
220  mock_clock_interface::clear();
221  EXPECT_CALL(mock_clock_interface::instance(), now())
222  .WillRepeatedly(Invoke([]() {
223  static int ts = 0;
224  return mock_clock::time_point(std::chrono::microseconds(++ts));
225  }));
226 
227  struct sleep {
228  MOCK_METHOD1(m, void(mock_clock::duration));
229  } sleeper;
230  auto mock_sleep = [&sleeper](mock_clock::duration d) { sleeper.m(d); };
231  EXPECT_CALL(sleeper, m(Truly([](auto const& d) {
232  return d == std::chrono::microseconds(2020);
233  })))
234  .Times(1);
235 
236  mock_sink sink;
237 
238  // ... create a pacer that commits up to 1024 bytes and blocks for
239  // up to a millisecond ...
243 
244  using namespace std::chrono;
245  // simulate 2 messages every 10 usecs, of different sizes and types ...
246  int msgcnt = 0;
248  'A', jb::itch5::timestamp{std::chrono::microseconds(5)}, 100);
249  p.handle_message(
250  mock_clock::now(),
251  jb::itch5::unknown_message(msgcnt++, 0, m1.size(), &m1[0]), sink,
252  mock_sleep);
253 
255  'B', jb::itch5::timestamp{std::chrono::microseconds(15)}, 90);
256  p.handle_message(
257  mock_clock::now(),
258  jb::itch5::unknown_message(msgcnt++, 0, m2.size(), &m2[0]), sink,
259  mock_sleep);
260 
261  // ... we expect that no messages have been sent so far ...
262  BOOST_CHECK_EQUAL(sink.packets.size(), 0);
263 
264  // ... the next message is much later ...
266  'A', jb::itch5::timestamp{std::chrono::microseconds(2025)}, 80);
267  p.handle_message(
268  mock_clock::now(),
269  jb::itch5::unknown_message(msgcnt++, 0, m3.size(), &m3[0]), sink,
270  mock_sleep);
271 
273  // ... that should immediately flush the first two messages ...
274  BOOST_REQUIRE_EQUAL(sink.packets.size(), 1);
275  BOOST_CHECK_EQUAL(hdrsize + 100 + 2 + 90 + 2, sink.packets.at(0).size());
276 }
277 
278 /**
279  * @test Verify that flush() on an empty packet does not produce a
280  * send() request.
281  */
282 BOOST_AUTO_TEST_CASE(itch5_mold_udp_pacer_flush_on_empty) {
283  int sleep_count = 0;
284  auto mock_sleep = [&sleep_count](mock_clock::duration) { ++sleep_count; };
285  using namespace ::testing;
286  mock_clock_interface::clear();
287  EXPECT_CALL(mock_clock_interface::instance(), now())
288  .WillRepeatedly(Invoke([]() {
289  static int ts = 0;
290  return mock_clock::time_point(std::chrono::microseconds(++ts));
291  }));
292  mock_sink sink;
293 
294  // ... create a pacer that commits up to 1024 bytes and blocks for
295  // up to a millisecond ...
299 
300  using namespace std::chrono;
301  jb::itch5::timestamp ts{std::chrono::microseconds(5)};
302  auto m = jb::itch5::testing::create_message('A', ts, 100);
303  p.handle_message(
304  mock_clock::now(), jb::itch5::unknown_message(0, 0, m.size(), &m[0]),
305  sink, mock_sleep);
306 
307  // ... we expect that no messages have been sent so far ...
308  BOOST_CHECK_EQUAL(sink.packets.size(), 0);
309 
310  // ... this flush() request should result in at least one packet ...
311  p.flush(ts, sink);
312 
314  // ... that should immediately flush the first two messages ...
315  BOOST_REQUIRE_EQUAL(sink.packets.size(), 1);
316  BOOST_CHECK_EQUAL(hdrsize + 100 + 2, sink.packets.at(0).size());
317 
318  // ... this flush() request should result in no more packets ...
319  p.flush(ts, sink);
320  BOOST_REQUIRE_EQUAL(sink.packets.size(), 1);
321 
322  // ... while a heartbeat() request should result in an additional
323  // packet ...
324  p.heartbeat(sink);
325  BOOST_REQUIRE_EQUAL(sink.packets.size(), 2);
326  BOOST_CHECK_EQUAL(hdrsize, sink.packets.at(1).size());
327 }
328 
329 /**
330  * @test Increase code coverage in jb::itch5::testing::create_message()
331  */
332 BOOST_AUTO_TEST_CASE(itch5_testing_create_message_errors) {
333  jb::itch5::timestamp ts{std::chrono::microseconds(1000)};
334 
335  // message too small ...
336  BOOST_CHECK_THROW(
337  jb::itch5::testing::create_message('A', ts, 2), std::exception);
338 
339  // ... message too big ...
340  BOOST_CHECK_THROW(
341  jb::itch5::testing::create_message('A', ts, 100000), std::exception);
342 
343  // ... message type out of range ...
344  BOOST_CHECK_THROW(
345  jb::itch5::testing::create_message(-1, ts, 100), std::exception);
346  BOOST_CHECK_THROW(
347  jb::itch5::testing::create_message(256, ts, 100), std::exception);
348 }
BOOST_AUTO_TEST_CASE(itch5_mold_udp_pacer_basic)
void handle_message(time_point ts, unknown_message const &msg, message_sink_type &sink, sleep_functor_type &sleeper)
Process a raw ITCH-5.x message.
clock_type::time_point time_point
A convenience alias for clock_type::time_point.
void heartbeat(message_sink_type &sink)
Send a heartbeat packet.
Send a sequence of raw ITCH-5.x messages as MoldUDP64 packets, trying to match the original time inte...
Represent a ITCH-5.0 timestamp.
Definition: timestamp.hpp:17
std::vector< char > create_message(int message_type, jb::itch5::timestamp ts, std::size_t total_size)
Generate test messages with a more-or-less valid header.
Definition: data.cpp:287
Initialize GMock to work with Boost.Test.
void flush(timestamp ts, message_sink_type &sink)
Flush the current messages, if any.
Configuration object for the jb::itch5::mold_udp_pacer class.
constexpr std::size_t header_size
The total size of the MoldUDP64 header.