Skip to content

Add test coverage for untested subsystems; fix CI coverage report#20

Draft
PazerOP wants to merge 26 commits into
masterfrom
claude/coverage-pass
Draft

Add test coverage for untested subsystems; fix CI coverage report#20
PazerOP wants to merge 26 commits into
masterfrom
claude/coverage-pass

Conversation

@PazerOP

@PazerOP PazerOP commented Jul 13, 2026

Copy link
Copy Markdown
Owner

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

Subsystem Before (line%) After (line%)
error/exception_details 0% 100%
error/ensure 0% (.inl) / 14.3% (.hpp) 100% / 100%
error/error_code_exception no data (untested) 100%
error/not_implemented_error 92.9% 93.3% (last line is exception-cleanup-only)
concurrency/thread_sentinel 0% 100%
concurrency/main_thread 0% 100%
concurrency/locked_value no data (untested) 100%
coroutine/thread.inl 64.7% 100%
data/lazy no data (untested) 100%
data/optional_ref no data (untested) 100%
text/codecvt.inl 64.7% 100%
text/multi_char 60% 100%
text/memstream 88.3% 99.0% (rest: an assert(false) line)
io/getopt 86.9% 100%
reflection/enum no data (never instantiated) 100%
text/filebuf.inl 91.7% 100%
text/formatters/error_code 93.5% 100%
source_location 88.7% 93.5% (rest: defensive/unreachable branches + a gcov artifact)
text/case_insensitive_string 95.0% 100%
concurrency/dispatcher.inl 83.7% 99.5% (rest: a post-assert return)
coroutine/task.hpp 89.1% 98.4% (rest: unreachable/race-window lines, see below)
concurrency/thread_pool.inl 73.6% 96.2% (rest: a catch(...) + assert arm)
coroutine/generator.hpp 89.8% 96.6% (rest: an unreachable default: arm)
future.hpp 90.9% 100%
io/fd_source.inl 90.5% 100%
io/fd_sink.inl 95.5% 100%
text/indenting_ostream 97.9% 100%
memory/unique_object 86.7% 100% (branches 25% → 75%)
memory/buffer.inl 89.6% 97.9% (rest: the realloc-failure throw)
data/bit_float 92.8% 100%
http/client.cpp 0% 95.9% (rest: curl library-init failure throws)
math/random.inl 0% 100%
io/filesystem_helpers.inl 0% 100%
data/bits.hpp 98.3% 100% (branches 71.4% → 85.7%)
error/expected.hpp 97.0% 100%
text/charconv_helper 97.0% 100%
process/process.inl 95.0% 98.3% (rest: the fork-failure branch)
math/interpolation.hpp 83.1% 84.5% (rest: constexpr-only round fallback — compile-time pinned, see below)
math/uint128.hpp 67.6% 67.6% (all remaining lines are constexpr-only software fallbacks — now compile-time pinned — plus two dead defensive throws, see below)
text/fmtstr.hpp 97.2% 97.2% (rest: constexpr-only puts branch — compile-time pinned)
algorithm/algorithm_generic no data (untested) 100%
algorithm/multi_compare no data (untested) 100%
math/angles no data (untested) 100%
utility.hpp no data (untested) 100%
types/enum_class_bit_ops no data (untested) 100%
overall (library) 82.4% 97.1%

(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 from exception_ptr for std::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 for std::error_condition and std::error_code instantiations, what() message inclusion, catchability as std::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 the convert_to_uc UTF-8/16/32 encoders incl. surrogate and >U+10FFFF rejection.
  • test/text_memstream_test.cpp (+10 cases) and test/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/f combinations), 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/full stream (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_write suspends 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 duplicate register_for_current_thread() throwing; an already-expired delay awaiter declining suspension via the await_suspend re-check.
  • test/coroutine_task_test.cpp (+9 cases): every wait/get on an empty task throwing future_error(no_state); take_value() consuming the value and invalidating the state (state→empty, subsequent waits throw); double completion throwing promise_already_satisfied; copy assignment for coroutine-backed, promise-backed, empty, and mixed tasks; wait_for/wait_until mid-wait arrival and timeout paths; exception results for int/string/void tasks including co_await propagation and the non-throwing void co_await; direct awaiter-interface conformance (ready-task await_suspend refusal, final_suspend await_resume); make_ready_task with a throwing value constructor unwinding cleanly.
  • test/concurrency_thread_pool_test.cpp (+5 cases): default ctor spinning up hardware_concurrency() threads; thread_pool(0) throwing invalid_argument; task_count() observing work queued behind a latch-blocked pool thread (deterministic, no sleeps); co_delay_until resuming no earlier than its deadline; co_add_task from 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 of promise::value()); dereferencing after a mid-generation exception rethrows the stored exception.
  • test/future_test.cpp (+1 case): emplace_ready_future capturing a throwing constructor into a failed future.
  • test/io_fd_test.cpp (+2 cases): fd_source::get_native_handle for owned and dup()ed descriptors; sink::create_file into a missing directory throwing with the real errno.
  • test/text_indenting_ostream_test.cpp (+1 case): negative sputn counts rejected with invalid_argument (would otherwise become a huge size_t read).
  • 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_t stream 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 on 127.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). Covers get_sync success (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-Length overpromise) throws, and the get() 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_random bounds over 1000 draws for all 10 instantiated types (incl. the 8-bit widen-to-16-bit delegation and full-domain ranges), degenerate min == max ranges 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) and replace_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 bool mirroring has_value().
  • test/text_charconv_helper_test.cpp (extended): bool from_chars propagating an underlying parse failure (non-numeric/empty input).
  • test/text_fmtstr_test.cpp (+1 case): compile-time pin of puts's constant-evaluation element-copy branch (the runtime branch uses memcpy), including the max_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_group visiting 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_compare with a custom comparison and all 18 generated helpers (any_eqall_less_equal), runtime + constexpr.
  • test/math_angles_test.cpp (1 case, new): deg2rad/rad2deg values, round trip, integral→float result-type promotion, constexpr usability.
  • test/utility_test.cpp (1 case, new): mh::copy producing an independent copy and feeding rvalue consumers.
  • test/types_enum_class_bit_ops_test.cpp (1 case, new): the MH_ENABLE_ENUM_CLASS_BIT_OPS operators — &/|/&=/|= 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

  • CI coverage report 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 of cpp/include, cpp/src and test/ — so the uploaded results_g++-*.html artifacts reported only Catch2 _deps sources. gcovr now runs from the repo root with the build dir as the search path and an explicit --filter on cpp/include/ + cpp/src/ (which also subsumes the old --exclude list). 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 declared MH_STUFF_API but 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 in thread_sentinel.inl; regression-tested by concurrency_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 bytes wcrtomb produced 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 past end so the loop read out of bounds. Fixed to ++it; regression-tested with 2-byte, 3-byte and mixed inputs in text_codecvt_test.cpp.
  • enum_fmt ostream insertion printed the type and value names with no separator (cpp/include/mh/reflection/enum.hpp): os << mh::enum_fmt(sound::boop) produced soundboop, while the formatter in the same header produces sound::boop (and unnamed values already print sound(1234)). The operator had never been instantiated anywhere. Added the "::" separator to match the formatter; regression-tested by reflection_enum_test.cpp.
  • get_random<float/double>(min, max) could return one ULP PAST max_inclusive (cpp/include/mh/math/random.inl): the floating-point arm builds uniform_real_distribution(min, nextafter(max, …)) to make the maximum reachable, but uniform_real_distribution may produce its upper bound itself due to floating-point rounding (the well-known LWG 2524 defect) — and that bound is deliberately one ULP past max_inclusive. In the degenerate min == max case this happened on ~50% of draws (probe: get_random<float>(3.25f, 3.25f) returned 3.250000238 on 5007 of 10000 calls). Fixed by clamping the result to max_inclusive; regression-tested by test/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: the operator<< lives in namespace mh, but when mh::source_location is an alias for std::source_location (all modern stdlibs) ADL cannot find it — consumers need using 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 rejects fmt::runtime()-wrapped wide format strings; std::string_view is not insertable into wide streams). Latent — no caller instantiates them today.
  • fmtstr.hpp base_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-copy failure 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_REQUIRE evaluations in the test suite force these exact branches at compile time and pin their results):

  • uint128.hpp 120-136, 149-151, 175-179, 244-255, 262, 288-299, 312, 332, 346, 396-399, 420-423 (44 lines): the portable software implementations of from_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_t paths run instead; the software paths execute only during constant evaluation.
  • interpolation.hpp 49-62 (11 lines): the round-half-away-from-zero fallback of detail::round, taken only when std::is_constant_evaluated() (runtime uses std::round).
  • fmtstr.hpp 84-85: puts's element-by-element copy, taken only under constant evaluation (runtime uses memcpy).

Dead defensive code (statically unreachable):

  • uint128.hpp 259, 303: throw "Should never get here..." in the shift operators — shift counts >= 128 return zero before the arm chain, so the final else cannot be entered.
  • task.hpp 113-114 and generator.hpp 67-68: default: arms of switches over state variants whose every index has an explicit case and which cannot become valueless.
  • source_location.hpp 163-164: the formatter's else-throw for an invalid PathPresentation; parse() rejects anything that would produce one.

Input impossible to construct:

  • source_location.hpp 153: a file_name() containing no path separator — mh::source_location aliases std::source_location on every CI leg, which cannot be hand-built, and compiler-produced file names always contain a separator.

Failure injection impossible in-process:

  • http/client.cpp 32, 38: curl_global_init / curl_easy_init failure throws (the curl library failing to initialize).
  • process.inl 69: fork() failure.
  • process_manager.inl 42-43: SIGCHLD handler registration failure.
  • buffer.inl 49: realloc failure.

Behind an assert that aborts first (the coverage build keeps assertions enabled):

  • chrono_helpers.inl 78 (time_ttm conversion failure), memstream.hpp 128 (unknown seekdir), dispatcher.inl 397 (run_one on the wrong thread), thread_pool.inl 71-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.hpp 24 (only std::string::append's bad_alloc could unwind here) and format.hpp 213 (build_string's local unwinds only if formatting "{}" throws).

Race-window (not deterministically testable):

  • task.hpp 235: await_suspend's double-checked inner early-return, reachable only when the task completes between the two is_ready() checks.

Coverage-tooling artifacts (the code demonstrably executes):

  • task.hpp 565: delete m_PromiseOpt in 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.hpp 142: 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:

  • Windows-only stack_info (no Windows CI leg).
  • The <cuchar>-based char↔charN_t conversions in codecvt, compiled out on every CI leg by the forced MH_BROKEN_UNICODE=1 (root CMakeLists); the UTF↔UTF conversions remain live and are tested.

🤖 Generated with Claude Code

https://claude.ai/code/session_016Uve5bsjBR5u9wRyvcyBXL

claude added 17 commits July 13, 2026 06:02
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
claude added 3 commits July 13, 2026 07:28
…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 PazerOP changed the title Claude/coverage pass Add test coverage for untested subsystems; fix CI coverage report Jul 13, 2026
@PazerOP
PazerOP marked this pull request as draft July 13, 2026 07:36
claude added 6 commits July 13, 2026 07:58
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants