Add test coverage for untested subsystems; fix CI coverage report#20
Draft
PazerOP wants to merge 26 commits into
Draft
Add test coverage for untested subsystems; fix CI coverage report#20PazerOP wants to merge 26 commits into
PazerOP wants to merge 26 commits into
Conversation
gcovr ran from inside the build dir with --root ../ and search path '.', which silently dropped every file not physically under the build dir -- all of cpp/include, cpp/src and test/. The uploaded results_g++-*.html artifacts therefore reported only Catch2 _deps sources. Run gcovr from the repo root with the build dir as the search path, and use an explicit --filter on cpp/include/ + cpp/src/ (which subsumes the old --exclude list: catch.hpp, test/, and the generated test_compile_file TUs all fall outside the filter). Verified locally against a warm g++-13 coverage build: the report now contains every library file, totals 95.4% lines / 52.2% branches (instantiation-weighted), matching the out-of-band measurement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
Covers exception_details construction from exception_ptr for std::exception, const char* (incl. null), std::string, nested exception chains, and unknown types; type_name(); and the custom-handler registry: add_handler, duplicate-registration rejection, handler invocation, RAII removal on destruction, move construction/assignment ownership transfer, and a register/unregister-from-2-threads-while-formatting smoke test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
Covers trigger_generic's report formatting via a cerr rdbuf capture:
expression text, right-justified in/at details labels, source file
attribution, optional message (present, absent, and empty-string),
streamable-value printing, and the '{typeid-name}' fallback for
non-streamable values via traits that force can_print_value(). Also
pins that a passing check prints nothing and that the failing path
passes the value through (reports to stderr, does not throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
error_code_exception: code() round-trip for std::error_condition and std::error_code instantiations, message inclusion in what(), and catchability as std::system_error. not_implemented_error: logic_error inheritance, what() describing the construction site (file, line, function), and the location() accessor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
…location()
thread_sentinel_exception::location() was declared MH_STUFF_API in
thread_sentinel.hpp but never defined anywhere: any caller failed at
link time in both header-only and compiled-library modes ('undefined
reference to mh::thread_sentinel_exception::location() const'). Define
it in thread_sentinel.inl like the rest of the class.
Tests cover: check() passing on the constructing thread, check() from
another thread throwing thread_sentinel_exception carrying the
expected/actual thread ids and trigger location in what() plus the
location() accessor, reset_id() rebinding to the calling thread, and
reset_id(id) with an explicit id.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
main_thread: is_main_thread() true on the Catch2 main thread, false on a worker; main_thread_id is program-wide (same value from any thread). coroutine/thread: co_create_thread resumes on a new thread; co_create_background_thread moves off the main thread but stays put when awaited from a non-main thread. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
locked_value: construction (default/copy/move), get/set and operator= in copy and move flavors, implicit conversion, lock()+get_ref (const and mutable), custom mutex type aliases, and a 4-thread increment race check. lazy: empty lazy throws logic_error, deferred single invocation with caching, reference stability across accessors, move-only result types, and retry-after-throw semantics. optional_ref: bound aliasing (get, conversion, write-through assignment), unbound writes landing in value-initialized internal storage, move vs copy assignment through to the target, deleted copy/move, and the optional-output-parameter use case. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
… advance change_encoding_impl<wchar_t, char> advanced the input iterator by the number of OUTPUT bytes wcrtomb produced instead of the one wide character it consumed. Any character encoding to 2+ bytes silently dropped the following input character(s); 3+ byte characters pushed the iterator past end() and the loop ran off the end of the input buffer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
Covers the near-zero-hit basic_memstreambuf<wchar_t> instantiation (seekpos/seekoff/underflow/overflow/setbuf), the empty-openmode throw, nonzero relative seeks, the overflow success path via an exposing subclass, and the unknown-seekdir throw (in its own NDEBUG TU with a TU-local traits type, since that branch asserts before throwing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
New: text_multi_char_test (accessors + comparisons for every character type), source_location_test (ostream inserter + formatter presentations and format_error paths). Extended: wide strwrapperstream insertion, write_file<wchar_t>, case_insensitive_char_traits<wchar_t>::lt and the base-traits converting constructors. Note: when mh::source_location aliases std::source_location, the inserter in namespace mh is invisible to ADL; the test imports it with a using-declaration (recorded as an open question). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
…eakers Covers the '?'/optopt short-name path, option-rejection by the callback, the empty/non-null-terminated longopt logic_errors, the optional_argument / numeric has_arg / flag-pointer branches of the option inserter, and the has_arg/flag/val comparison tie-breakers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
reflection/enum.hpp had never been instantiated by any test or consumer.
New tests cover type names (namespaced + global), name<->value lookups
(hits, misses, throws), the free helper functions, enum_value accessors,
enum_fmt ostream insertion, and the formatter presentations + error
paths.
The ostream inserter printed the type and value names with no separator
("soundboop"); the formatter in the same header emits "sound::boop".
Added the "::" separator to match.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
filebuf: writing to an unbuffered /dev/full FILE exercises the fputc-EOF branch of overflow and the short-fwrite path of xsputn (SKIPs if /dev/full is unavailable). error_code formatter: unknown presentation characters and unterminated specs throw format_error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
…hrow tests - co_wait_fd_write: suspends on a full pipe, resumes once drained - mixed fd polls: not-ready read/write fds survive while others resume - delay heap: later-added-but-earlier-due task fires first - get() with no registration and duplicate register_for_current_thread throw - expired co_delay_task awaiter declines suspension (await_suspend race re-check) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
…, exception results - empty task wait/wait_for/wait_until/get throw future_error(no_state) - take_value() consumes the value and invalidates (state -> empty, waits throw) - double return_value throws promise_already_satisfied - copy assignment for coroutine-, promise-backed, empty, and mixed tasks - wait_for/wait_until ready-after-blocking and timeout paths - exception results for int/string/void tasks incl. co_await propagation - direct awaiter-interface checks (ready-task await_suspend, final_suspend await_resume) - make_ready_task with throwing value constructor unwinds cleanly Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
…o_delay_until, inline fast path - default ctor spins up hardware_concurrency threads and runs work - thread_pool(0) throws invalid_argument - task_count() observes work queued behind a latch-blocked pool thread (deterministic, no sleeps) - co_delay_until resumes no earlier than the deadline - co_add_task from a pool thread is a ready no-op awaiter (no thread hop) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
… test - dereferencing an empty/exhausted generator throws (no-state arm of promise::value) - dereferencing after a mid-generation exception rethrows the stored exception - emplace_ready_future captures a throwing constructor into a failed future Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
…ect ostream, buffer move tests - fd_source::get_native_handle for owned and dup()ed descriptors - sink::create_file into a missing directory throws with the real errno - indenting_ostream rejects negative sputn counts with invalid_argument - unique_object stream insertion prints the value or (empty) - buffer move constructor transfers the allocation and empties the source Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
…ponent, component stream insertion - 1e30f/-1e30f -> half overflows to clean +-inf at runtime - float NaN -> half keeps a full exponent and nonzero mantissa payload - all-zero exponent field converts to 0 in wider and narrower widths - exponent_t/mantissa_t stream insertion prints numerically Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
… return Raw gcov showed wait_until's post-block ready path and the non-throwing return out of promise<void>::await_resume were still unexecuted (every existing void co_await awaited a failed task). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
PazerOP
marked this pull request as draft
July 13, 2026 07:36
Raw POSIX socket server on 127.0.0.1 (ephemeral port, 100ms-poll slices bounded by stop flag + 30s deadline so nothing can hang CI). Covers get_sync success (status/body/header-value trimming incl. tight/empty values), redirect following, connection-refused and truncated-response throws, and the get() coroutine's success + error-propagation paths. Gated on MH_COMPILE_LIBRARY (cpp/src only builds then) && __unix__. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
…ng max_inclusive get_random<float/double>(min, max) could return one ULP PAST max_inclusive: uniform_real_distribution may produce its upper bound b due to floating-point rounding (LWG 2524), and b is deliberately nextafter(max). With min == max, ~50% of draws exceeded the bound (probe: 5007/10000 returned 3.250000238 for get_random(3.25f, 3.25f)). Clamp the result to max_inclusive. Tests assert only deterministic properties: range bounds over 1000 draws for all 10 instantiated types, degenerate ranges, full-domain 8-bit widening delegation, thread_local engine on a fresh thread. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
… runtime lines - uint128: STATIC_CHECK the software ++/+/-=/<</>> arms (carry, borrow, cross-half shifts) that only execute during constant evaluation on x86-64; the shift operators' "Should never get here" throws stay dead defensive code behind the early >=128 returns. - interpolation: runtime in-range clamp pass-through (the all-float arm). - bits: bit_read out-of-range bit_count precondition throws. - expected: operator bool. - charconv_helper: bool from_chars with a failing underlying parse. - fmtstr: constexpr puts element-copy branch incl. truncation clamp. - process: pre-start/post-completion terminate refusal, one-shot start. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
…, enum_class_bit_ops Five headers with real logic but no test had ever instantiated them: for_each_multimap_group grouping/empty cases, any/none/all_compare plus all 18 generated comparison helpers, deg2rad/rad2deg values + result-type promotion, mh::copy independence, and the enum-class bit-ops macro's &(=)/|(=) operators with the truthiness wrapper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
Returning the base_format_string from the constexpr lambda odr-used its implicit copy constructor, which is deprecated (the class user-declares a copy assignment operator) - a -Werror failure on all four clang legs (gcc's -Wdeprecated-copy ignores '= default' assignment operators). Do all the checks inside the lambda and return a bool instead; verified against a local clang-18 + libc++ build of the full suite (350 green). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
Resolves the test/CMakeLists.txt conflict by keeping the sorted union of both PRs' mh_test entries. Merged suite: 381 cases, all passing locally on g++-13 (coverage build) and clang++-18 + libc++. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Test-coverage pass following #19. Baseline measured on the g++-13 gcov leg at e01daf9: 82.4% line / 50.2% branch / 98.9% function coverage (distinct-physical-line view, 222/222 tests passing). This PR closes the testable gaps subsystem by subsystem — adding tests for entirely-untested files and the untested failure paths of partially-tested ones, fixing any real bugs those tests expose — and fixes the CI coverage report, which previously uploaded HTML containing no library files at all.
Coverage
assert(false)line)assertreturn)catch(...)+assertarm)default:arm)realloc-failure throw)fork-failure branch)roundfallback — compile-time pinned, see below)putsbranch — compile-time pinned)(measured on g++-13, COMPILE_LIBRARY=ON, distinct-physical-line view; branch coverage overall: 50.2% → 63.9%, function coverage 98.9% → 99.6%)
Tests added
test/error_exception_details_test.cpp(11 cases): exception_details fromexception_ptrforstd::exception,const char*(incl. null),std::string, nested exception chains, unknown types; the custom-handler registry: add_handler, duplicate-registration rejection, handler invocation, RAII removal, move ctor/assignment ownership transfer, 2-thread register/unregister smoke while formatting.test/error_ensure_test.cpp(+5 cases): the mh_ensure failure path — stderr report formatting (expression text, in/at labels, file attribution), message present/absent/empty, streamable-value printing,{typeid-name}fallback for non-streamable values, passing check prints nothing. (The failure path reports and passes the value through; it does not throw or abort.)test/error_error_code_exception_test.cpp(5 cases): code() round-trip forstd::error_conditionandstd::error_codeinstantiations, what() message inclusion, catchability asstd::system_error.test/error_not_implemented_error_test.cpp(3 cases): logic_error inheritance, what() describing the call site, location() accessor.test/concurrency_thread_sentinel_test.cpp(4 cases): same-thread check passes, cross-thread check throws with expected/actual ids + location in what() and via location(), reset_id() rebinding, reset_id(id) explicit.test/concurrency_main_thread_test.cpp(2 cases): is_main_thread() on main and worker threads; main_thread_id is program-wide.test/coroutine_thread_test.cpp(3 cases): co_create_thread resumes on a new thread; co_create_background_thread moves off the main thread and stays put on a non-main thread.test/concurrency_locked_value_test.cpp(5 cases): full API (ctors, get/set copy+move, operator=/conversion, lock()+get_ref const/mutable, custom mutex) plus a 4-thread increment race check.test/data_lazy_test.cpp(5 cases): empty lazy throws, deferred single invocation + caching, reference stability, move-only results, retry-after-throw.test/data_optional_ref_test.cpp(5 cases): bound aliasing/write-through, unbound internal-storage writes, move vs copy assignment through, deleted copy/move, optional-output-parameter use case.test/text_codecvt_test.cpp(+8 cases): char↔wchar_t via mbrtowc/wcrtomb — ASCII and multi-byte round trips, embedded nulls, truncated-sequence/invalid-byte/lone-surrogate error paths (UTF-8-locale tests SKIP where no UTF-8 locale exists); the To==From identity conversions; direct tests of theconvert_to_ucUTF-8/16/32 encoders incl. surrogate and >U+10FFFF rejection.test/text_memstream_test.cpp(+10 cases) andtest/text_memstream_seekdir_test.cpp(new TU): the whole wide (wchar_t) instantiation — seeks in every mode, underflow→eof, full-buffer behavior, single-character visibility; setbuf for both widths; empty-openmode throw; nonzero relative seeks; the overflow success path via an exposing subclass; the unknown-seekdir throw (own TU with NDEBUG + a TU-local traits type, since that branch asserts before throwing).test/text_multi_char_test.cpp(3 cases, new):get<T>()for every character type (incl. pointer-type normalization),==and<=>in both argument orders.test/source_location_test.cpp(3 cases, new): the ostream inserter (narrow + wide streams), formatter presentations (p/P/l/fcombinations), and the format_error paths (mutually-exclusive p/P, unknown character, unterminated spec).test/io_getopt_test.cpp(+6 cases): unknown-option '?'/optopt reporting, callback rejection of an option, empty / non-null-terminated longopt array logic_errors, the option ostream inserter (optional_argument, numeric has_arg, null name, flag pointer), comparison tie-breakers (name/has_arg/flag/val).test/reflection_enum_test.cpp(8 cases, new): first-ever instantiation of reflection/enum.hpp — type names (namespaced + global scope), value→name and name→value lookups (hits, misses, throws), all six free helper functions, enum_value accessors, negative/INT_MAX/uint8 edge values, enum_fmt ostream insertion, formatter presentations (t/T/v) and its format_error paths.test/text_string_insertion_test.cpp(+1 case): the wide strwrapperstream (ctor, xsputn, overflow, formatted insertion).test/io_file_test.cpp(+1 case):write_file<wchar_t>(string_view + const-pointer overloads).test/text_case_insensitive_string_test.cpp(+1 case, extended):case_insensitive_char_traits<wchar_t>::lt, base-traits converting constructors for both widths.test/text_filebuf_test.cpp(+1 case): write errors from the FILE* — the fputc-EOF branch of overflow and short-fwrite propagation, via an unbuffered/dev/fullstream (SKIPs if unavailable).test/text_format_test.cpp(+1 case): error_code formatter format_error paths (unknown character, unterminated spec).test/concurrency_dispatcher_test.cpp(+5 cases): the fd-WRITE readiness chain (co_wait_fd_writesuspends on a full pipe, resumes once drained); mixed polls where a not-ready read/write fd must survive while another fd's task resumes; delay tasks firing in deadline order (not insertion order) through the delay heap's comparator;get()with no registered dispatcher and duplicateregister_for_current_thread()throwing; an already-expired delay awaiter declining suspension via theawait_suspendre-check.test/coroutine_task_test.cpp(+9 cases): every wait/get on an empty task throwingfuture_error(no_state);take_value()consuming the value and invalidating the state (state→empty, subsequent waits throw); double completion throwingpromise_already_satisfied; copy assignment for coroutine-backed, promise-backed, empty, and mixed tasks;wait_for/wait_untilmid-wait arrival and timeout paths; exception results for int/string/void tasks includingco_awaitpropagation and the non-throwing voidco_await; direct awaiter-interface conformance (ready-taskawait_suspendrefusal,final_suspendawait_resume);make_ready_taskwith a throwing value constructor unwinding cleanly.test/concurrency_thread_pool_test.cpp(+5 cases): default ctor spinning uphardware_concurrency()threads;thread_pool(0)throwinginvalid_argument;task_count()observing work queued behind a latch-blocked pool thread (deterministic, no sleeps);co_delay_untilresuming no earlier than its deadline;co_add_taskfrom a pool thread continuing inline (ready no-op awaiter, no thread hop).test/coroutine_generator_test.cpp(+2 cases): dereferencing an empty/exhausted generator throws (the no-state arm ofpromise::value()); dereferencing after a mid-generation exception rethrows the stored exception.test/future_test.cpp(+1 case):emplace_ready_futurecapturing a throwing constructor into a failed future.test/io_fd_test.cpp(+2 cases):fd_source::get_native_handlefor owned anddup()ed descriptors;sink::create_fileinto a missing directory throwing with the real errno.test/text_indenting_ostream_test.cpp(+1 case): negativesputncounts rejected withinvalid_argument(would otherwise become a hugesize_tread).test/memory_unique_object_test.cpp(+1 case): stream insertion printing the value or(empty).test/memory_buffer_test.cpp(+1 case): the move constructor transferring the allocation and emptying the source.test/data_bit_float_test.cpp(+2 cases): runtime narrowing edges — float→half overflow to clean ±inf, NaN keeping a full exponent + nonzero mantissa payload, the all-zero exponent field converting to 0 in any width;exponent_t/mantissa_tstream insertion printing numerically.test/http_client_test.cpp(+6 cases): first runtime tests for the HTTP client, against an in-process loopback server (raw POSIX socket on127.0.0.1, ephemeral port, canned HTTP/1.1 responses; every wait is a 100 ms poll slice bounded by a stop flag and a 30 s deadline, so no test can hang CI; no external network). Coversget_syncsuccess (status/body, header-value whitespace trimming incl. no-space and empty values, the no-colon status/blank lines), redirect following (CURLOPT_FOLLOWLOCATION, headers accumulating across hops), connection-refused and truncated-response (Content-Lengthoverpromise) throws, and theget()coroutine's off-thread success + exception propagation through the task. Requests are captured and asserted (GET /path HTTP/1.1,Host:).test/math_random_test.cpp(11 cases, new):get_randombounds over 1000 draws for all 10 instantiated types (incl. the 8-bit widen-to-16-bit delegation and full-domain ranges), degeneratemin == maxranges returning exactly the bound, negative ranges, and the thread_local engine working on a fresh thread. Only deterministic properties are asserted.test/io_filesystem_helpers_test.cpp(2 cases, new):filename_without_extension(nested paths, multi-extension, extensionless, dotfiles, trailing slash) andreplace_filename_keep_extension(extension adoption/replacement/stripping, directory preservation).test/math_uint128_test.cpp(+1 case): compile-time pins for the portable software fallbacks that only execute during constant evaluation on x86-64 (the runtime paths use__uint128_t):++/+/-=carry & borrow across the halves,<</>>in the 1..63 (cross-half) and 64..127 arms.test/math_interpolation_test.cpp(+1 case): the all-floating clamp arm's in-range pass-through (values strictly inside the bounds come back unclamped).test/data_bits_test.cpp(+1 case):bit_read's runtime precondition throws (bit count exceeding the source or destination width), plus the exact-boundary success.test/error_expected_test.cpp(extended):operator boolmirroringhas_value().test/text_charconv_helper_test.cpp(extended): boolfrom_charspropagating an underlying parse failure (non-numeric/empty input).test/text_fmtstr_test.cpp(+1 case): compile-time pin ofputs's constant-evaluation element-copy branch (the runtime branch usesmemcpy), including themax_size()truncation clamp.test/process_process_test.cpp(extended):start()is one-shot (second call refused),terminate()refused before start and after completion.test/algorithm_algorithm_generic_test.cpp(2 cases, new):for_each_multimap_groupvisiting each key-group exactly once with its full range; empty and single-group containers.test/algorithm_multi_compare_test.cpp(2 cases, new):any/none/all_comparewith a custom comparison and all 18 generated helpers (any_eq…all_less_equal), runtime + constexpr.test/math_angles_test.cpp(1 case, new):deg2rad/rad2degvalues, round trip, integral→float result-type promotion, constexpr usability.test/utility_test.cpp(1 case, new):mh::copyproducing an independent copy and feeding rvalue consumers.test/types_enum_class_bit_ops_test.cpp(1 case, new): theMH_ENABLE_ENUM_CLASS_BIT_OPSoperators —&/|/&=/|=and the result wrapper's truthiness (if (x & flag)), runtime + constexpr.Suite: 222 → 371 test cases, all passing. (The branch has since merged master's #21 — upgradable mutex, +10 cases — so ctest on the branch reports 381; the coverage figures above were measured at the 371-case state.)
Bugs found and fixed
.github/workflows/ccpp.yml): the workflow ran gcovr from inside the build dir with--root ../and search path.. gcovr silently dropped every file not physically under the build dir — all ofcpp/include,cpp/srcandtest/— so the uploadedresults_g++-*.htmlartifacts reported only Catch2_depssources. gcovr now runs from the repo root with the build dir as the search path and an explicit--filteroncpp/include/+cpp/src/(which also subsumes the old--excludelist). Verified locally against a warm coverage build: all instrumented library files now appear in the report.thread_sentinel_exception::location()declared but never defined (cpp/include/mh/concurrency/thread_sentinel.hpp:19): the accessor was declaredMH_STUFF_APIbut had no definition in the .inl (or anywhere), so any caller failed at link time in both header-only and compiled-library modes (undefined reference to mh::thread_sentinel_exception::location() const, g++-13). Fixed by defining it inthread_sentinel.inl; regression-tested byconcurrency_thread_sentinel_test.cpp.change_encoding<char>dropped/overran input on multi-byte characters (cpp/include/mh/text/codecvt.inl,change_encoding_impl<wchar_t, char>): the conversion loop advanced the INPUT iterator by the number of OUTPUT byteswcrtombproduced instead of the one wide character it consumed. In any multi-byte locale, a 2-byte character silently skipped the next input character (change_encoding<char>(L"éé")returned only"é"), and a 3+-byte character pushed the iterator pastendso the loop read out of bounds. Fixed to++it; regression-tested with 2-byte, 3-byte and mixed inputs intext_codecvt_test.cpp.cpp/include/mh/reflection/enum.hpp):os << mh::enum_fmt(sound::boop)producedsoundboop, while the formatter in the same header producessound::boop(and unnamed values already printsound(1234)). The operator had never been instantiated anywhere. Added the"::"separator to match the formatter; regression-tested byreflection_enum_test.cpp.get_random<float/double>(min, max)could return one ULP PASTmax_inclusive(cpp/include/mh/math/random.inl): the floating-point arm buildsuniform_real_distribution(min, nextafter(max, …))to make the maximum reachable, butuniform_real_distributionmay produce its upper bound itself due to floating-point rounding (the well-known LWG 2524 defect) — and that bound is deliberately one ULP pastmax_inclusive. In the degeneratemin == maxcase this happened on ~50% of draws (probe:get_random<float>(3.25f, 3.25f)returned3.250000238on 5007 of 10000 calls). Fixed by clamping the result tomax_inclusive; regression-tested bytest/math_random_test.cpp's degenerate-range checks.Open questions for the maintainer (found, not changed)
exception_details.inl:81:handler test(nullptr);inside add_handler is a dead statement — delete?source_location.hpp: theoperator<<lives in namespacemh, but whenmh::source_locationis an alias forstd::source_location(all modern stdlibs) ADL cannot find it — consumers needusing mh::operator<<;. Effectively dead API; intended usage?reflection/enum.hpp: the wide (wchar_t) branches of both the enum_fmt formatter and its ostream inserter do not compile when instantiated (fmt 9 rejectsfmt::runtime()-wrapped wide format strings;std::string_viewis not insertable into wide streams). Latent — no caller instantiates them today.fmtstr.hppbase_format_string: a user-declared copy assignment operator but no copy constructor makes the implicit copy constructor deprecated — copy-constructing one is a-Werror -Wdeprecated-copyfailure on the clang legs (and the implicit move constructor is suppressed). Add defaulted copy/move constructors, or is non-copy-constructibility intended?Deliberately not covered
The final report has 82 uncovered lines across the whole library. Every one of them is listed here with its reason.
Constexpr-only code, pinned by compile-time checks instead (gcov cannot observe constant evaluation;
STATIC_CHECK/STATIC_REQUIREevaluations in the test suite force these exact branches at compile time and pin their results):uint128.hpp120-136, 149-151, 175-179, 244-255, 262, 288-299, 312, 332, 346, 396-399, 420-423 (44 lines): the portable software implementations offrom_mul,++,+,-=,<<,>>,get_u128/set_u128(bit_cast) and the mixed<=>comparisons. At runtime on every CI leg (x86-64)is_constant_evaluated()is false and the native__uint128_tpaths run instead; the software paths execute only during constant evaluation.interpolation.hpp49-62 (11 lines): the round-half-away-from-zero fallback ofdetail::round, taken only whenstd::is_constant_evaluated()(runtime usesstd::round).fmtstr.hpp84-85:puts's element-by-element copy, taken only under constant evaluation (runtime usesmemcpy).Dead defensive code (statically unreachable):
uint128.hpp259, 303:throw "Should never get here..."in the shift operators — shift counts >= 128 return zero before the arm chain, so the finalelsecannot be entered.task.hpp113-114 andgenerator.hpp67-68:default:arms of switches over state variants whose every index has an explicitcaseand which cannot become valueless.source_location.hpp163-164: the formatter'selse-throw for an invalidPathPresentation;parse()rejects anything that would produce one.Input impossible to construct:
source_location.hpp153: afile_name()containing no path separator —mh::source_locationaliasesstd::source_locationon every CI leg, which cannot be hand-built, and compiler-produced file names always contain a separator.Failure injection impossible in-process:
http/client.cpp32, 38:curl_global_init/curl_easy_initfailure throws (the curl library failing to initialize).process.inl69:fork()failure.process_manager.inl42-43:SIGCHLDhandler registration failure.buffer.inl49:reallocfailure.Behind an
assertthat aborts first (the coverage build keeps assertions enabled):chrono_helpers.inl78 (time_t→tmconversion failure),memstream.hpp128 (unknown seekdir),dispatcher.inl397 (run_oneon the wrong thread),thread_pool.inl71-73 (catch(...)around a pool task).Exception-cleanup-only
}lines (raw gcov shows=====: the counter belongs to the unwind path, and no argument the function accepts can make its body throw):not_implemented_error.hpp24 (onlystd::string::append'sbad_alloccould unwind here) andformat.hpp213 (build_string's local unwinds only if formatting"{}"throws).Race-window (not deterministically testable):
task.hpp235:await_suspend's double-checked inner early-return, reachable only when the task completes between the twois_ready()checks.Coverage-tooling artifacts (the code demonstrably executes):
task.hpp565:delete m_PromiseOptin the non-coroutine release lambda — gcovr's template-specialization merge reports it uncovered, but raw gcov shows the aggregate line executed and an ASan probe confirms the promise is freed.source_location.hpp142: the intro line of the formatter's immediately-invoked lambda — raw gcov attributes a never-emitted closure-constructor record to it while the lambda's body lines execute (16 hits each).Whole-file/leg exclusions:
stack_info(no Windows CI leg).<cuchar>-based char↔charN_t conversions incodecvt, compiled out on every CI leg by the forcedMH_BROKEN_UNICODE=1(root CMakeLists); the UTF↔UTF conversions remain live and are tested.🤖 Generated with Claude Code
https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL