Skip to content

Commit 637a90d

Browse files
committed
test: run io_context loop tests on every backend, add small gap tests
Parameterize the io_context suite on the backend tag so run, run_one, poll, run_for, stop/restart, and the multithreaded tests exercise each scheduler's loop machinery instead of only the default backend's. The dedicated constructor-overload checks keep their default-backend forms. Add edge tests: IPv6 parsing (uppercase hex, full-form embedded IPv4, truncated and over-full group forms, malformed dotted octets), the datagram connect_pair open-socket rejection, byte-sized IPv4 multicast option round-trips (covering the single-byte getsockopt readback normalization), v6_only, and the closed-acceptor accept/wait throw guards including the returning accept() overload.
1 parent b8832dd commit 637a90d

5 files changed

Lines changed: 151 additions & 38 deletions

File tree

test/unit/io_context.cpp

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,25 @@ post_coro(io_context::executor_type& ex, std::coroutine_handle<> h)
238238
ex.post(h);
239239
}
240240

241+
inline capy::task<capy::io_result<>>
242+
set_event_task(capy::async_event& evt)
243+
{
244+
evt.set();
245+
co_return capy::io_result<>{std::error_code{}};
246+
}
247+
248+
inline capy::task<void>
249+
when_all_set_event_main(bool& finished)
250+
{
251+
capy::async_event evt;
252+
auto [ec, a, b] = co_await capy::when_all(evt.wait(), set_event_task(evt));
253+
(void)a;
254+
(void)b;
255+
BOOST_TEST(!ec);
256+
finished = true;
257+
}
258+
259+
template<auto Backend>
241260
struct io_context_test
242261
{
243262
void testConstruction()
@@ -278,7 +297,7 @@ struct io_context_test
278297
{
279298
io_context_options opts;
280299
opts.thread_pool_size = 4;
281-
io_context ioc(opts, 2);
300+
io_context ioc(Backend, opts, 2);
282301
BOOST_TEST(!ioc.stopped());
283302
}
284303

@@ -287,7 +306,7 @@ struct io_context_test
287306
// concurrency_hint == 1 enables single-threaded mode automatically.
288307
io_context_options opts;
289308
opts.single_threaded = true;
290-
io_context ioc(opts, 1);
309+
io_context ioc(Backend, opts, 1);
291310
BOOST_TEST(!ioc.stopped());
292311

293312
int counter = 0;
@@ -300,7 +319,7 @@ struct io_context_test
300319

301320
void testGetExecutor()
302321
{
303-
io_context ioc;
322+
io_context ioc(Backend);
304323
auto ex = ioc.get_executor();
305324

306325
// Executor should be valid
@@ -310,7 +329,7 @@ struct io_context_test
310329

311330
void testRun()
312331
{
313-
io_context ioc;
332+
io_context ioc(Backend);
314333
auto ex = ioc.get_executor();
315334
int counter = 0;
316335

@@ -327,7 +346,7 @@ struct io_context_test
327346

328347
void testRunOne()
329348
{
330-
io_context ioc;
349+
io_context ioc(Backend);
331350
auto ex = ioc.get_executor();
332351
int counter = 0;
333352

@@ -349,7 +368,7 @@ struct io_context_test
349368

350369
void testPoll()
351370
{
352-
io_context ioc;
371+
io_context ioc(Backend);
353372
auto ex = ioc.get_executor();
354373
int counter = 0;
355374

@@ -373,7 +392,7 @@ struct io_context_test
373392

374393
void testPollOne()
375394
{
376-
io_context ioc;
395+
io_context ioc(Backend);
377396
auto ex = ioc.get_executor();
378397
int counter = 0;
379398

@@ -405,7 +424,7 @@ struct io_context_test
405424

406425
void testStopAndRestart()
407426
{
408-
io_context ioc;
427+
io_context ioc(Backend);
409428
auto ex = ioc.get_executor();
410429
int counter = 0;
411430

@@ -435,7 +454,7 @@ struct io_context_test
435454

436455
void testRunOneFor()
437456
{
438-
io_context ioc;
457+
io_context ioc(Backend);
439458
auto ex = ioc.get_executor();
440459
int counter = 0;
441460

@@ -457,7 +476,7 @@ struct io_context_test
457476

458477
void testRunOneUntil()
459478
{
460-
io_context ioc;
479+
io_context ioc(Backend);
461480
auto ex = ioc.get_executor();
462481
int counter = 0;
463482

@@ -488,7 +507,7 @@ struct io_context_test
488507
// is the deterministic form of a valgrind/CI flake where the thread
489508
// is preempted past the deadline before the first loop check, so
490509
// wait_one (which holds the "no work -> stop" logic) was skipped.
491-
io_context ioc;
510+
io_context ioc(Backend);
492511

493512
auto past =
494513
std::chrono::steady_clock::now() - std::chrono::milliseconds(1);
@@ -505,7 +524,7 @@ struct io_context_test
505524

506525
void testRunFor()
507526
{
508-
io_context ioc;
527+
io_context ioc(Backend);
509528
auto ex = ioc.get_executor();
510529
int counter = 0;
511530

@@ -532,7 +551,7 @@ struct io_context_test
532551

533552
void testRunForWithOutstandingWork()
534553
{
535-
io_context ioc;
554+
io_context ioc(Backend);
536555
auto ex = ioc.get_executor();
537556

538557
// Simulate persistent outstanding work (like a listening acceptor)
@@ -555,7 +574,7 @@ struct io_context_test
555574

556575
void testRunOneForWithOutstandingWork()
557576
{
558-
io_context ioc;
577+
io_context ioc(Backend);
559578
auto ex = ioc.get_executor();
560579

561580
ex.on_work_started();
@@ -576,7 +595,7 @@ struct io_context_test
576595

577596
void testExecutorRunningInThisThread()
578597
{
579-
io_context ioc;
598+
io_context ioc(Backend);
580599
auto ex = ioc.get_executor();
581600

582601
// Not running yet - should return false
@@ -592,7 +611,7 @@ struct io_context_test
592611

593612
void testMultithreaded()
594613
{
595-
io_context ioc;
614+
io_context ioc(Backend);
596615
auto ex = ioc.get_executor();
597616
std::atomic<int> counter{0};
598617
constexpr int num_threads = 4;
@@ -636,7 +655,7 @@ struct io_context_test
636655

637656
for (int iter = 0; iter < iterations; ++iter)
638657
{
639-
io_context ioc;
658+
io_context ioc(Backend);
640659
auto ex = ioc.get_executor();
641660
std::atomic<int> counter{0};
642661

@@ -664,22 +683,6 @@ struct io_context_test
664683
}
665684
}
666685

667-
static capy::task<capy::io_result<>> set_event_task(capy::async_event& evt)
668-
{
669-
evt.set();
670-
co_return capy::io_result<>{{}};
671-
}
672-
673-
static capy::task<void> when_all_set_event_main(bool& finished)
674-
{
675-
capy::async_event evt;
676-
auto [ec, a, b] = co_await capy::when_all(evt.wait(), set_event_task(evt));
677-
(void)a;
678-
(void)b;
679-
BOOST_TEST(!ec);
680-
finished = true;
681-
}
682-
683686
void testWhenAllSetEvent()
684687
{
685688
io_context ctx;
@@ -696,7 +699,7 @@ struct io_context_test
696699
int destroyed = 0;
697700

698701
{
699-
io_context ioc;
702+
io_context ioc(Backend);
700703
auto ex = ioc.get_executor();
701704

702705
post_coro(ex, make_destroy_coro(destroyed));
@@ -726,7 +729,7 @@ struct io_context_test
726729
op2.cont.h = make_destroy_coro(destroyed);
727730

728731
{
729-
io_context ioc;
732+
io_context ioc(Backend);
730733
auto ex = ioc.get_executor();
731734

732735
ex.post(op1.cont);
@@ -744,7 +747,7 @@ struct io_context_test
744747
// iterates with rel_time clamped to 1s before returning 0.
745748
void testRunOneUntilLongDeadlineNoWork()
746749
{
747-
io_context ioc;
750+
io_context ioc(Backend);
748751

749752
// Deadline >1s but tiny outstanding work so wait_one is not
750753
// entered: scheduler is empty, wait_one immediately stops and
@@ -766,7 +769,7 @@ struct io_context_test
766769
// timing.
767770
void testMultithreadedNotifyAndWaitFor()
768771
{
769-
io_context ioc; // default hint => MT mode
772+
io_context ioc(Backend); // default hint => MT mode
770773
auto ex = ioc.get_executor();
771774
std::atomic<int> counter{0};
772775

@@ -821,7 +824,7 @@ struct io_context_test
821824
}
822825
};
823826

824-
TEST_SUITE(io_context_test, "boost.corosio.io_context");
827+
COROSIO_BACKEND_TESTS(io_context_test, "boost.corosio.io_context")
825828

826829
// Backend-parameterized tests for shutdown paths that differ per backend
827830
template<auto Backend>

test/unit/ipv6_address.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,36 @@ struct ipv6_address_test
186186
BOOST_TEST(parse_ipv6_address("::g.0.0.0", addr));
187187
// "1:2:3:4:5:6.7.8.9" — IPv4 with no '::' but not enough h16 groups.
188188
BOOST_TEST(parse_ipv6_address("1:2:3:4:5:6.7.8.9", addr));
189+
// The embedded-IPv4 validator parses each octet as an h16 and
190+
// rejects values dotted decimal can never produce.
191+
BOOST_TEST(parse_ipv6_address("::1.2.3.400", addr));
192+
BOOST_TEST(parse_ipv6_address("::1.2.3.2a", addr));
193+
BOOST_TEST(parse_ipv6_address("::1.2.3.a1", addr));
194+
}
195+
196+
void testParseMoreEdges()
197+
{
198+
ipv6_address addr;
199+
200+
// Uppercase hex digits.
201+
BOOST_TEST(!parse_ipv6_address("ABCD::EF01", addr));
202+
BOOST_TEST_EQ(addr.to_string(), "abcd::ef01");
203+
204+
// Full-form embedded IPv4 with no '::'.
205+
BOOST_TEST(!parse_ipv6_address("1:2:3:4:5:6:1.2.3.4", addr));
206+
207+
// Input ending right after a colon.
208+
BOOST_TEST(parse_ipv6_address("1:", addr));
209+
BOOST_TEST(parse_ipv6_address("1:2:3:4:5:6:7:", addr));
210+
211+
// Non-hex garbage after '::'.
212+
BOOST_TEST(parse_ipv6_address("1::zz", addr));
213+
214+
// '::' with all eight groups already present.
215+
BOOST_TEST(parse_ipv6_address("1:2:3:4:5:6:7:8::", addr));
216+
217+
// '::' compressing exactly zero remaining groups at the end.
218+
BOOST_TEST(!parse_ipv6_address("1:2:3:4:5:6:7::", addr));
189219
}
190220

191221
void testPredicates()
@@ -245,6 +275,7 @@ struct ipv6_address_test
245275
testParse();
246276
testParseEndsWithDoubleColon();
247277
testParseInvalidIPv4Suffix();
278+
testParseMoreEdges();
248279
testToString();
249280
testToStringHexWidths();
250281
testToBuffer();

test/unit/local_connect_pair.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,17 @@ struct local_connect_pair_test
9191
}
9292

9393
#if BOOST_COROSIO_POSIX
94+
void testDatagramPairRejectsOpenSocket()
95+
{
96+
io_context ioc(Backend);
97+
local_datagram_socket a(ioc), b(ioc);
98+
b.open();
99+
auto ec = connect_pair(a, b);
100+
BOOST_TEST(static_cast<bool>(ec));
101+
BOOST_TEST(!a.is_open());
102+
BOOST_TEST(b.is_open());
103+
}
104+
94105
void testDatagramPairExchangesData()
95106
{
96107
io_context ioc(Backend);
@@ -141,6 +152,7 @@ struct local_connect_pair_test
141152
testStreamPairExchangesData();
142153
testStreamPairRejectsOpenSocket();
143154
#if BOOST_COROSIO_POSIX
155+
testDatagramPairRejectsOpenSocket();
144156
testDatagramPairExchangesData();
145157
#endif
146158
}

test/unit/socket_option.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,42 @@ struct socket_option_test
114114
sock.close();
115115
}
116116

117+
// The byte-sized IPv4 multicast options exercise the option
118+
// resize() normalization for getsockopt writing a single byte.
119+
void testMulticastOptions()
120+
{
121+
io_context ioc(Backend);
122+
udp_socket sock(ioc);
123+
sock.open(udp::v4());
124+
125+
sock.set_option(socket_option::multicast_loop_v4(false));
126+
BOOST_TEST(
127+
!sock.get_option<socket_option::multicast_loop_v4>().value());
128+
sock.set_option(socket_option::multicast_loop_v4(true));
129+
BOOST_TEST(
130+
sock.get_option<socket_option::multicast_loop_v4>().value());
131+
132+
sock.set_option(socket_option::multicast_hops_v4(5));
133+
BOOST_TEST_EQ(
134+
sock.get_option<socket_option::multicast_hops_v4>().value(), 5);
135+
136+
sock.close();
137+
}
138+
139+
void testV6Only()
140+
{
141+
io_context ioc(Backend);
142+
udp_socket sock(ioc);
143+
sock.open(udp::v6());
144+
145+
sock.set_option(socket_option::v6_only(true));
146+
BOOST_TEST(sock.get_option<socket_option::v6_only>().value());
147+
sock.set_option(socket_option::v6_only(false));
148+
BOOST_TEST(!sock.get_option<socket_option::v6_only>().value());
149+
150+
sock.close();
151+
}
152+
117153
void testClosedSocketThrows()
118154
{
119155
io_context ioc(Backend);
@@ -209,6 +245,8 @@ struct socket_option_test
209245
testTcpOptions();
210246
testTcpLocalEndpoint();
211247
testUdpOptions();
248+
testMulticastOptions();
249+
testV6Only();
212250
testClosedSocketThrows();
213251
testInvalidOptionReportsError();
214252
#if BOOST_COROSIO_POSIX

0 commit comments

Comments
 (0)