8 #include "xclox/ntp/query_series.hpp"
10 #include "tools/server.hpp"
11 #include "tools/tracer.hpp"
13 #include "tools/helper.hpp"
15 using namespace xclox::ntp;
16 using namespace std::chrono;
18 TEST_SUITE(
"QuerySeries")
22 : server1(32101, serverTracer1.callable())
23 , server2(32102, serverTracer2.callable())
24 , server3(32103, serverTracer3.callable())
27 Tracer<asio::ip::udp::endpoint, asio::error_code, const uint8_t*, size_t> serverTracer1, serverTracer2, serverTracer3;
28 Tracer<asio::ip::udp::endpoint, asio::error_code, Packet, steady_clock::duration> queryTracer;
29 Server server1, server2, server3;
33 TEST_CASE_FIXTURE(Context,
"no callback or endpoints")
36 QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(server1.endpoint(),
"",
""), {});
37 QuerySeries::start(io, asio::ip::udp::resolver::results_type(), queryTracer.callable());
41 TEST_CASE_FIXTURE(Context,
"single query fails")
43 QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(broadcastEndpoint,
"",
""), queryTracer.callable());
45 CHECK(queryTracer.counter() == 1);
46 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
47 return endpoint == broadcastEndpoint && error == asio::error::access_denied && isClientPacket(packet) && compare(rtt, milliseconds(1));
51 TEST_CASE_FIXTURE(Context,
"single query succeeds")
54 QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(server1.endpoint(),
"",
""), queryTracer.callable());
56 CHECK(queryTracer.counter() == 1);
57 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
58 return endpoint == server1.endpoint() && !error && isClientPacket(packet) && compare(rtt, milliseconds(1));
62 TEST_CASE_FIXTURE(Context,
"two queries fail")
64 const auto& localEndpoint = asio::ip::udp::endpoint(asio::ip::make_address(
"0.0.0.0"), 1234);
65 std::vector<asio::ip::udp::endpoint> endpointList { localEndpoint, broadcastEndpoint };
66 QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(endpointList.begin(), endpointList.end(),
"",
""), queryTracer.callable());
68 CHECK(queryTracer.counter() == 1);
69 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
70 return endpoint == broadcastEndpoint && error == asio::error::access_denied && isClientPacket(packet) && compare(rtt, milliseconds(1));
74 TEST_CASE_FIXTURE(Context,
"first query fails, second query times out, third query succeeds")
77 server1.replay(&data, 0);
80 std::vector<asio::ip::udp::endpoint> endpointList { server1.endpoint(), server2.endpoint(), server3.endpoint() };
81 QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(endpointList.begin(), endpointList.end(),
"",
""), queryTracer.callable());
83 CHECK(queryTracer.counter() == 1);
84 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
85 return endpoint == server3.endpoint() && !error && isClientPacket(packet) && compare(rtt, milliseconds(1));
87 CHECK(serverTracer1.wait(2) == 2);
88 CHECK(serverTracer2.wait(1) == 1);
89 CHECK(serverTracer3.wait(2) == 2);
92 TEST_CASE_FIXTURE(Context,
"traceable")
94 SUBCASE(
"single-target")
96 auto query =
QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(broadcastEndpoint,
"",
""), queryTracer.callable());
97 CHECK_FALSE(query.expired());
99 CHECK(queryTracer.counter() == 1);
100 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
101 return endpoint == broadcastEndpoint && error == asio::error::access_denied && !packet.isNull() && compare(rtt, milliseconds(1));
103 CHECK(query.expired());
105 SUBCASE(
"multi-target")
108 std::vector<asio::ip::udp::endpoint> endpointList { server1.endpoint(), broadcastEndpoint };
109 auto query =
QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(endpointList.begin(), endpointList.end(),
"",
""), queryTracer.callable());
110 CHECK_FALSE(query.expired());
112 CHECK(serverTracer1.wait(2) == 2);
113 CHECK(queryTracer.counter() == 1);
114 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
115 return endpoint == server1.endpoint() && !error && !packet.isNull() && compare(rtt, milliseconds(1));
117 CHECK(query.expired());
121 const auto& someEndpoint = asio::ip::udp::endpoint(asio::ip::make_address(
"254.254.254.254"), 1234);
123 std::vector<asio::ip::udp::endpoint> endpointList { broadcastEndpoint, server1.endpoint(), someEndpoint };
124 auto query =
QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(endpointList.begin(), endpointList.end(),
"",
""), queryTracer.callable());
125 CHECK_FALSE(query.expired());
127 CHECK(serverTracer1.wait(2) == 2);
128 CHECK(queryTracer.counter() == 1);
129 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
130 return endpoint == server1.endpoint() && !error && !packet.isNull() && compare(rtt, milliseconds(1));
132 CHECK(query.expired());
136 TEST_CASE_FIXTURE(Context,
"cancellable")
138 SUBCASE(
"before running a query")
140 auto query =
QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(broadcastEndpoint,
"",
""), queryTracer.callable());
141 query.lock()->cancel();
143 CHECK(queryTracer.wait() == 1);
144 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
145 return endpoint == broadcastEndpoint && error == asio::error::operation_aborted && !packet.isNull() && compare(rtt, milliseconds(1));
147 CHECK(query.expired());
149 SUBCASE(
"during the first query")
152 auto query =
QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(server1.endpoint(),
"",
""), queryTracer.callable());
156 CHECK(serverTracer1.wait() == 1);
157 query.lock()->cancel();
158 CHECK(queryTracer.wait() == 1);
159 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
160 return endpoint == server1.endpoint() && error == asio::error::operation_aborted && packet.isNull() && compare(rtt, milliseconds(1));
162 CHECK(query.expired());
164 SUBCASE(
"during the second query")
167 server1.replay(&data, 1);
170 std::vector<asio::ip::udp::endpoint> endpointList { server1.endpoint(), server2.endpoint(), server3.endpoint() };
171 auto query =
QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(endpointList.begin(), endpointList.end(),
"",
""), queryTracer.callable());
175 CHECK(serverTracer1.wait(2) == 2);
176 CHECK(serverTracer2.wait() == 1);
177 query.lock()->cancel();
178 CHECK(queryTracer.wait() == 1);
179 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
180 return endpoint == server2.endpoint() && error == asio::error::operation_aborted && packet.isNull() && compare(rtt, milliseconds(1));
182 CHECK(serverTracer3.counter() == 0);
183 CHECK(query.expired());
185 SUBCASE(
"multiple cancellations")
187 auto query =
QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(broadcastEndpoint,
"",
""), queryTracer.callable());
188 auto handle = query.lock();
192 CHECK(io.run() == 0);
196 TEST_CASE_FIXTURE(Context,
"custom timeout")
198 for (
int i = 0; i < 3; ++i) {
200 const auto& start = steady_clock::now();
201 auto query =
QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(server1.endpoint(),
"",
""), queryTracer.callable(), milliseconds(i * 100));
204 CHECK(compare(start, milliseconds(i * 100)));
205 CHECK(query.expired());
206 CHECK(queryTracer.counter() == 1);
207 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
208 return endpoint == server1.endpoint() && error == asio::error::timed_out && packet.isNull() && compare(rtt, milliseconds(i * 100));
214 TEST_CASE_FIXTURE(Context,
"default timeout")
217 server2.receive(milliseconds(QuerySingle::DefaultTimeout::ms + 1000));
218 const auto& start = steady_clock::now();
219 std::vector<asio::ip::udp::endpoint> endpointList { server1.endpoint(), server2.endpoint() };
220 auto query =
QuerySeries::start(io, asio::ip::udp::resolver::results_type::create(endpointList.begin(), endpointList.end(),
"",
""), queryTracer.callable());
222 CHECK(compare(start, milliseconds(QuerySeries::DefaultTimeout::ms)));
223 CHECK(query.expired());
224 CHECK(queryTracer.counter() == 1);
225 CHECK(queryTracer.find([&](
const asio::ip::udp::endpoint& endpoint,
const asio::error_code& error,
const Packet& packet,
const steady_clock::duration& rtt) {
226 return endpoint == server2.endpoint() && error == asio::error::timed_out && packet.isNull() && compare(rtt, milliseconds(QuerySeries::DefaultTimeout::ms - QuerySingle::DefaultTimeout::ms));
Packet is an immutable raw NTP packet.
Definition: packet.hpp:54
static std::weak_ptr< QuerySeries > start(asio::io_context &io, const asio::ip::udp::resolver::results_type &endpoints, Callback callback, const std::chrono::milliseconds &timeout=std::chrono::milliseconds(DefaultTimeout::ms))
Starts querying the given endpoints one at a time until success or all endpoints are queried.
Definition: query_series.hpp:52