Skip to content

Commit 2b3568d

Browse files
committed
test: lift unit coverage from 81% to ~92% on Linux
Adds targeted unit tests across the public API and native shadow layers to close the largest line-coverage gaps in the develop baseline. Major areas: - tls_context: cover malformed PEM, mismatched cert/key, cipher list, protocol version round-trip, verify mode/depth, hostname, SNI/ALPN, CRL, OCSP staple, password callback (24% to 100%). - io_context and scheduler detail: backend tag construction, run_for, run_until, restart, post-throws, deterministic multithreaded notify_one + wait_for coverage. - socket_option: set/get round-trip for every public option plus the native templated variants; wrong-protocol error paths. - local sockets: connect/accept error paths, abstract namespace, path-length boundary, mid-flight cancel, datagram send/recv. Adopt the temp_socket_dir helper introduced upstream. - reactor internals: concurrent read/write on same descriptor, mid-flight cancel, close-during-op, wait_type::error paths, stop-token cancellation. Add reactor_paths.cpp aggregating these scenarios. - tcp_server, posix_resolver_service, host_name, file services: lifecycle, accept loop, flag variants, error mapping. - Add testConstructionWithThreadPoolSize regression for the resolver use_service fix. Cross-platform reconciliation observed in CI: - BSD-family kernels reject multicast set_option values (zero buffer sizes, leave-without-route, IPV6 ifindex 0) that Linux accepts; wrap in try/catch with documented platform variation. - macOS returns EMSGSIZE for zero-length UDP datagrams; broaden the expectation to any error so MinGW and others pass too. - Windows IOCP: gate testIoContextOptionsMaxEventsZero/BudgetInitClamp and the single-threaded resolver tests as POSIX-only; relax buffer size assertions to permit Windows's accept-zero-as-zero contract. - POSIX-guard local socket tests that exercise abstract namespace and related Linux-only behavior. - testMultithreadedNotifyAndWaitFor restructured to depend only on a work guard and counter drain, not on wall-clock timing, so it remains deterministic under thread-sanitizer. - Signal-set shutdown test switched from POSIX-only SIGUSR1/2 to the portable SIGINT/SIGTERM pair.
1 parent 7bbebc7 commit 2b3568d

26 files changed

Lines changed: 5109 additions & 213 deletions

‎test/unit/io_context.cpp‎

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
// Test that header file is self-contained.
1111
#include <boost/corosio/io_context.hpp>
1212

13+
#include <boost/corosio/detail/continuation_op.hpp>
14+
1315
#include <boost/capy/ex/async_event.hpp>
1416
#include <boost/capy/ex/run_async.hpp>
1517
#include <boost/capy/task.hpp>
@@ -253,6 +255,49 @@ struct io_context_test
253255
}
254256
}
255257

258+
void testConstructionWithOptions()
259+
{
260+
// Tune reactor budgets (POSIX) and IOCP gqcs timeout so the
261+
// option-applying constructor path exercises non-default values.
262+
io_context_options opts;
263+
opts.max_events_per_poll = 256;
264+
opts.inline_budget_initial = 4;
265+
opts.inline_budget_max = 32;
266+
opts.unassisted_budget = 8;
267+
opts.gqcs_timeout_ms = 250;
268+
269+
io_context ioc(opts, 2);
270+
BOOST_TEST(!ioc.stopped());
271+
272+
// Single-arg constructor with options + default concurrency
273+
io_context ioc2(opts);
274+
BOOST_TEST(!ioc2.stopped());
275+
}
276+
277+
void testConstructionWithThreadPoolSize()
278+
{
279+
io_context_options opts;
280+
opts.thread_pool_size = 4;
281+
io_context ioc(opts, 2);
282+
BOOST_TEST(!ioc.stopped());
283+
}
284+
285+
void testConstructionSingleThreaded()
286+
{
287+
// concurrency_hint == 1 enables single-threaded mode automatically.
288+
io_context_options opts;
289+
opts.single_threaded = true;
290+
io_context ioc(opts, 1);
291+
BOOST_TEST(!ioc.stopped());
292+
293+
int counter = 0;
294+
auto ex = ioc.get_executor();
295+
post_coro(ex, make_coro(counter));
296+
std::size_t n = ioc.run();
297+
BOOST_TEST(n == 1);
298+
BOOST_TEST(counter == 1);
299+
}
300+
256301
void testGetExecutor()
257302
{
258303
io_context ioc;
@@ -642,9 +687,95 @@ struct io_context_test
642687
BOOST_TEST_EQ(destroyed, 3);
643688
}
644689

690+
// Exercises continuation_op::destroy() — invoked when shutdown drains
691+
// queued continuation_op posts. The tagged-post path through
692+
// executor::post(capy::continuation&) routes to scheduler::post(scheduler_op*)
693+
// which enqueues without heap allocation; on shutdown the queue is drained
694+
// and destroy() must release each continuation's coroutine frame.
695+
void testContinuationOpDestroyOnShutdown()
696+
{
697+
int destroyed = 0;
698+
699+
// Allocate the continuation_ops outside the io_context scope so the
700+
// ops outlive the scheduler that points at them.
701+
detail::continuation_op op1;
702+
detail::continuation_op op2;
703+
op1.cont.h = make_destroy_coro(destroyed);
704+
op2.cont.h = make_destroy_coro(destroyed);
705+
706+
{
707+
io_context ioc;
708+
auto ex = ioc.get_executor();
709+
710+
ex.post(op1.cont);
711+
ex.post(op2.cont);
712+
713+
// io_context destructor drains scheduler queue and calls
714+
// continuation_op::destroy() on each.
715+
}
716+
717+
BOOST_TEST_EQ(destroyed, 2);
718+
}
719+
720+
// Exercises the `rel_time > 1s` clamp branch in run_one_until.
721+
// With no work and a deadline >1s in the future, the inner loop
722+
// iterates with rel_time clamped to 1s before returning 0.
723+
void testRunOneUntilLongDeadlineNoWork()
724+
{
725+
io_context ioc;
726+
727+
// Deadline >1s but tiny outstanding work so wait_one is not
728+
// entered: scheduler is empty, wait_one immediately stops and
729+
// returns 0. The outer run_one_until loop still enters with
730+
// rel_time > 1s, hitting the clamp branch.
731+
auto deadline =
732+
std::chrono::steady_clock::now() + std::chrono::seconds(2);
733+
std::size_t n = ioc.run_one_until(deadline);
734+
BOOST_TEST(n == 0);
735+
BOOST_TEST(ioc.stopped());
736+
}
737+
738+
// MT-mode test that exercises conditionally_enabled_event::wait_for()
739+
// and cross-thread notify_one(). A work guard keeps run_for inside its
740+
// wait_for; main posts 8 handlers from a different thread, each of
741+
// which triggers notify_one. Main polls counter until all handlers
742+
// have run, then releases the work guard so run_for exits. The
743+
// assertion depends only on the completed work, not on wall-clock
744+
// timing.
745+
void testMultithreadedNotifyAndWaitFor()
746+
{
747+
io_context ioc; // default hint => MT mode
748+
auto ex = ioc.get_executor();
749+
std::atomic<int> counter{0};
750+
751+
// Work guard prevents run_for from short-circuiting on an
752+
// empty queue before main posts any work.
753+
ex.on_work_started();
754+
755+
std::thread runner([&]() {
756+
// 5s ceiling is a safety net only; we release the guard
757+
// below as soon as work is drained.
758+
(void)ioc.run_for(std::chrono::seconds(5));
759+
});
760+
761+
for (int i = 0; i < 8; ++i)
762+
post_coro(ex, make_atomic_coro(counter));
763+
764+
while (counter.load() < 8)
765+
std::this_thread::yield();
766+
767+
ex.on_work_finished();
768+
runner.join();
769+
770+
BOOST_TEST_EQ(counter.load(), 8);
771+
}
772+
645773
void run()
646774
{
647775
testConstruction();
776+
testConstructionWithOptions();
777+
testConstructionWithThreadPoolSize();
778+
testConstructionSingleThreaded();
648779
testGetExecutor();
649780
testRun();
650781
testRunOne();
@@ -653,14 +784,17 @@ struct io_context_test
653784
testStopAndRestart();
654785
testRunOneFor();
655786
testRunOneUntil();
787+
testRunOneUntilLongDeadlineNoWork();
656788
testRunFor();
657789
testRunForWithOutstandingWork();
658790
testRunOneForWithOutstandingWork();
659791
testExecutorRunningInThisThread();
660792
testMultithreaded();
661793
testMultithreadedStress();
794+
testMultithreadedNotifyAndWaitFor();
662795
testWhenAllSetEvent();
663796
testShutdownDestroysPostedCoroutineFrames();
797+
testContinuationOpDestroyOnShutdown();
664798
}
665799
};
666800

@@ -686,9 +820,24 @@ struct io_context_shutdown_test
686820
BOOST_TEST_EQ(destroyed, 3);
687821
}
688822

823+
void testConstructionWithBackendAndOptions()
824+
{
825+
// Exercises the templated io_context(Backend, options, hint)
826+
// constructor that runs apply_options_pre_/_post_.
827+
io_context_options opts;
828+
opts.max_events_per_poll = 64;
829+
opts.inline_budget_initial = 4;
830+
opts.inline_budget_max = 16;
831+
opts.unassisted_budget = 4;
832+
833+
io_context ioc(Backend, opts, 2);
834+
BOOST_TEST(!ioc.stopped());
835+
}
836+
689837
void run()
690838
{
691839
testShutdownDestroysPostedCoroutineFrames();
840+
testConstructionWithBackendAndOptions();
692841
}
693842
};
694843

‎test/unit/ipv6_address.cpp‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,47 @@ struct ipv6_address_test
147147
BOOST_TEST_EQ(sv, "::1");
148148
}
149149

150+
void testToBufferTooSmallThrows()
151+
{
152+
// to_buffer must throw length_error when the buffer is smaller
153+
// than max_str_len, even if the formatted address would fit.
154+
char small[4];
155+
BOOST_TEST_THROWS(
156+
ipv6_address::loopback().to_buffer(small, sizeof(small)),
157+
std::length_error);
158+
}
159+
160+
void testToStringHexWidths()
161+
{
162+
// Exercise each print_hex width branch (4, 3, 2, 1 hex digit).
163+
// 4 digits: 0xabcd, 3 digits: 0x0bcd, 2 digits: 0x00bc, 1 digit: 0x000b.
164+
ipv6_address::bytes_type b{
165+
{0xab, 0xcd, 0x0b, 0xcd, 0x00, 0xbc, 0x00, 0x0b,
166+
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}};
167+
ipv6_address a(b);
168+
BOOST_TEST_EQ(a.to_string(), "abcd:bcd:bc:b:1234:5678:9abc:def0");
169+
}
170+
171+
void testParseEndsWithDoubleColon()
172+
{
173+
// "1::" — '::' at the end requires the "ends in ::" hex break path.
174+
ipv6_address addr;
175+
auto ec = parse_ipv6_address("1::", addr);
176+
BOOST_TEST(!ec);
177+
BOOST_TEST_EQ(addr.to_string(), "1::");
178+
}
179+
180+
void testParseInvalidIPv4Suffix()
181+
{
182+
ipv6_address addr;
183+
// "::1.2.3" — IPv4 portion incomplete.
184+
BOOST_TEST(parse_ipv6_address("::1.2.3", addr));
185+
// "::g.0.0.0" — non-numeric hex.
186+
BOOST_TEST(parse_ipv6_address("::g.0.0.0", addr));
187+
// "1:2:3:4:5:6.7.8.9" — IPv4 with no '::' but not enough h16 groups.
188+
BOOST_TEST(parse_ipv6_address("1:2:3:4:5:6.7.8.9", addr));
189+
}
190+
150191
void testPredicates()
151192
{
152193
// Unspecified
@@ -202,8 +243,12 @@ struct ipv6_address_test
202243
{
203244
testConstruction();
204245
testParse();
246+
testParseEndsWithDoubleColon();
247+
testParseInvalidIPv4Suffix();
205248
testToString();
249+
testToStringHexWidths();
206250
testToBuffer();
251+
testToBufferTooSmallThrows();
207252
testPredicates();
208253
testComparison();
209254
testOstream();

0 commit comments

Comments
 (0)