Skip to content

Commit 82acbe7

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 d2bb93f commit 82acbe7

5 files changed

Lines changed: 133 additions & 22 deletions

File tree

test/unit/io_context.cpp

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

241+
template<auto Backend>
241242
struct io_context_test
242243
{
243244
void testConstruction()
@@ -278,7 +279,7 @@ struct io_context_test
278279
{
279280
io_context_options opts;
280281
opts.thread_pool_size = 4;
281-
io_context ioc(opts, 2);
282+
io_context ioc(Backend, opts, 2);
282283
BOOST_TEST(!ioc.stopped());
283284
}
284285

@@ -287,7 +288,7 @@ struct io_context_test
287288
// concurrency_hint == 1 enables single-threaded mode automatically.
288289
io_context_options opts;
289290
opts.single_threaded = true;
290-
io_context ioc(opts, 1);
291+
io_context ioc(Backend, opts, 1);
291292
BOOST_TEST(!ioc.stopped());
292293

293294
int counter = 0;
@@ -300,7 +301,7 @@ struct io_context_test
300301

301302
void testGetExecutor()
302303
{
303-
io_context ioc;
304+
io_context ioc(Backend);
304305
auto ex = ioc.get_executor();
305306

306307
// Executor should be valid
@@ -310,7 +311,7 @@ struct io_context_test
310311

311312
void testRun()
312313
{
313-
io_context ioc;
314+
io_context ioc(Backend);
314315
auto ex = ioc.get_executor();
315316
int counter = 0;
316317

@@ -327,7 +328,7 @@ struct io_context_test
327328

328329
void testRunOne()
329330
{
330-
io_context ioc;
331+
io_context ioc(Backend);
331332
auto ex = ioc.get_executor();
332333
int counter = 0;
333334

@@ -349,7 +350,7 @@ struct io_context_test
349350

350351
void testPoll()
351352
{
352-
io_context ioc;
353+
io_context ioc(Backend);
353354
auto ex = ioc.get_executor();
354355
int counter = 0;
355356

@@ -373,7 +374,7 @@ struct io_context_test
373374

374375
void testPollOne()
375376
{
376-
io_context ioc;
377+
io_context ioc(Backend);
377378
auto ex = ioc.get_executor();
378379
int counter = 0;
379380

@@ -405,7 +406,7 @@ struct io_context_test
405406

406407
void testStopAndRestart()
407408
{
408-
io_context ioc;
409+
io_context ioc(Backend);
409410
auto ex = ioc.get_executor();
410411
int counter = 0;
411412

@@ -435,7 +436,7 @@ struct io_context_test
435436

436437
void testRunOneFor()
437438
{
438-
io_context ioc;
439+
io_context ioc(Backend);
439440
auto ex = ioc.get_executor();
440441
int counter = 0;
441442

@@ -457,7 +458,7 @@ struct io_context_test
457458

458459
void testRunOneUntil()
459460
{
460-
io_context ioc;
461+
io_context ioc(Backend);
461462
auto ex = ioc.get_executor();
462463
int counter = 0;
463464

@@ -488,7 +489,7 @@ struct io_context_test
488489
// is the deterministic form of a valgrind/CI flake where the thread
489490
// is preempted past the deadline before the first loop check, so
490491
// wait_one (which holds the "no work -> stop" logic) was skipped.
491-
io_context ioc;
492+
io_context ioc(Backend);
492493

493494
auto past =
494495
std::chrono::steady_clock::now() - std::chrono::milliseconds(1);
@@ -505,7 +506,7 @@ struct io_context_test
505506

506507
void testRunFor()
507508
{
508-
io_context ioc;
509+
io_context ioc(Backend);
509510
auto ex = ioc.get_executor();
510511
int counter = 0;
511512

@@ -532,7 +533,7 @@ struct io_context_test
532533

533534
void testRunForWithOutstandingWork()
534535
{
535-
io_context ioc;
536+
io_context ioc(Backend);
536537
auto ex = ioc.get_executor();
537538

538539
// Simulate persistent outstanding work (like a listening acceptor)
@@ -555,7 +556,7 @@ struct io_context_test
555556

556557
void testRunOneForWithOutstandingWork()
557558
{
558-
io_context ioc;
559+
io_context ioc(Backend);
559560
auto ex = ioc.get_executor();
560561

561562
ex.on_work_started();
@@ -576,7 +577,7 @@ struct io_context_test
576577

577578
void testExecutorRunningInThisThread()
578579
{
579-
io_context ioc;
580+
io_context ioc(Backend);
580581
auto ex = ioc.get_executor();
581582

582583
// Not running yet - should return false
@@ -592,7 +593,7 @@ struct io_context_test
592593

593594
void testMultithreaded()
594595
{
595-
io_context ioc;
596+
io_context ioc(Backend);
596597
auto ex = ioc.get_executor();
597598
std::atomic<int> counter{0};
598599
constexpr int num_threads = 4;
@@ -636,7 +637,7 @@ struct io_context_test
636637

637638
for (int iter = 0; iter < iterations; ++iter)
638639
{
639-
io_context ioc;
640+
io_context ioc(Backend);
640641
auto ex = ioc.get_executor();
641642
std::atomic<int> counter{0};
642643

@@ -696,7 +697,7 @@ struct io_context_test
696697
int destroyed = 0;
697698

698699
{
699-
io_context ioc;
700+
io_context ioc(Backend);
700701
auto ex = ioc.get_executor();
701702

702703
post_coro(ex, make_destroy_coro(destroyed));
@@ -726,7 +727,7 @@ struct io_context_test
726727
op2.cont.h = make_destroy_coro(destroyed);
727728

728729
{
729-
io_context ioc;
730+
io_context ioc(Backend);
730731
auto ex = ioc.get_executor();
731732

732733
ex.post(op1.cont);
@@ -744,7 +745,7 @@ struct io_context_test
744745
// iterates with rel_time clamped to 1s before returning 0.
745746
void testRunOneUntilLongDeadlineNoWork()
746747
{
747-
io_context ioc;
748+
io_context ioc(Backend);
748749

749750
// Deadline >1s but tiny outstanding work so wait_one is not
750751
// entered: scheduler is empty, wait_one immediately stops and
@@ -766,7 +767,7 @@ struct io_context_test
766767
// timing.
767768
void testMultithreadedNotifyAndWaitFor()
768769
{
769-
io_context ioc; // default hint => MT mode
770+
io_context ioc(Backend); // default hint => MT mode
770771
auto ex = ioc.get_executor();
771772
std::atomic<int> counter{0};
772773

@@ -821,7 +822,7 @@ struct io_context_test
821822
}
822823
};
823824

824-
TEST_SUITE(io_context_test, "boost.corosio.io_context");
825+
COROSIO_BACKEND_TESTS(io_context_test, "boost.corosio.io_context")
825826

826827
// Backend-parameterized tests for shutdown paths that differ per backend
827828
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

test/unit/tcp_acceptor.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
#include <boost/corosio/socket_option.hpp>
1515
#include <boost/corosio/tcp.hpp>
1616
#include <boost/corosio/timer.hpp>
17+
#include <boost/corosio/wait_type.hpp>
1718

1819
#include <boost/capy/cond.hpp>
1920
#include <boost/capy/ex/run_async.hpp>
2021
#include <boost/capy/task.hpp>
2122

2223
#include <chrono>
24+
#include <stdexcept>
2325
#include <stop_token>
2426

2527
#ifndef _WIN32
@@ -717,6 +719,32 @@ struct tcp_acceptor_test
717719
BOOST_TEST_EQ(acc.is_open(), false);
718720
}
719721

722+
// accept()/wait() on a closed acceptor must throw rather than
723+
// initiate an operation on an invalid handle.
724+
void testClosedAcceptorOpsThrow()
725+
{
726+
io_context ioc(Backend);
727+
tcp_acceptor acc(ioc);
728+
tcp_socket peer(ioc);
729+
730+
auto expect_throw = [](auto fn) {
731+
bool threw = false;
732+
try
733+
{
734+
fn();
735+
}
736+
catch (std::logic_error const&)
737+
{
738+
threw = true;
739+
}
740+
BOOST_TEST(threw);
741+
};
742+
743+
expect_throw([&] { (void)acc.accept(peer); });
744+
expect_throw([&] { (void)acc.accept(); });
745+
expect_throw([&] { (void)acc.wait(wait_type::read); });
746+
}
747+
720748
// Stop-token cancel of a parked accept. Unlike testCancelAccept
721749
// (acceptor-wide cancel()), this routes through the per-waiter
722750
// stop callback.
@@ -901,6 +929,7 @@ struct tcp_acceptor_test
901929
testBindError();
902930
testListenClosedThrows();
903931
testClosedAcceptorAccessors();
932+
testClosedAcceptorOpsThrow();
904933

905934
// Waiter lifecycle
906935
testStopTokenAccept();

0 commit comments

Comments
 (0)