Raise line coverage to 99.9 %: fix the measurement, remove unreachable code, add tests (#5997)

* CI: measure line coverage without lcov's unexecuted-block adjustment

Drop geninfo_unexecuted_blocks=1 from the lcov capture of ci_test_coverage:
it reset every line of an inlined or templated function to 0 as soon as one
copy had an unexecuted block, which understated the line coverage by about
three points.

Add -fvar-tracking-assignments to both coverage builds. At -O0, GCC inlines
always_inline functions before it places the coverage counters, so the call
sites of such helpers (put_literal() and others) got no line record and
showed as 0 in an -O3 translation unit that never reaches them. With the
flag, the call sites carry counts in every translation unit. The generated
code is identical.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Remove unreachable code found by the coverage analysis

Remove code that no input can reach, and mark the lines that cannot be
covered with LCOV_EXCL_LINE:

- BJData writer: the dry_run parameter of the ND-array element writers was
  always true; replace them by range checks.
- BON8 writer: call throw_integer_out_of_range() directly instead of
  write_bon8_value() for values that always throw.
- binary_writer: integer_bits() is only called for integers of up to 64 bits.
- serializer, view serializer, lexer: replace guards that the callers make
  unreachable by assertions or by the simpler code (surrogate pairs under
  ensure_ascii, next_unget, '-' at the end of the buffer).
- json_view: drop unreachable branches in scalar(), set(), check_utf8_scan(),
  append_input_impl() and unescape().
- Mark exception-cleanup braces, 2 GiB limits, big-endian and 32-bit code, and
  fall-through case labels with LCOV_EXCL_LINE.

No behavior changes for any input that reaches these functions.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Tests: cover the remaining lines of the library headers

Add tests for the lines of include/nlohmann that were not covered: the
binary readers and writers, the lexer and locale paths, the json_view
modules, the custom number, key and array types, json_pointer, json_patch,
diff and the iterators.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix test failures in the coverage tests

- unit-custom-object-key-type: remove the unused make_strict_key(strict_key)
  overload, the unused range constructor and default constructor, and guard
  data() with JSON_DIAGNOSTICS (unused-function/-member-function errors with
  GCC and clang -Weverything).
- unit-json_view_compare: move the two new sections to the new
  unit-json_view_compare_values.cpp, as the object file got too large for the
  MinGW assembler again, and use a std::map with std::greater instead of
  std::unordered_map, which does not accept the incomplete basic_json
  (clang 3.9, icpc).
- unit-json_view_dump: rename the variables that hid outer ones (MSVC C4456).
- unit-stream-bulk-scan: restore the real size of the oversized get area
  before the adapter is destroyed (JSON_PRECISE_STREAM_POSITION reads the
  character at the read position), and skip the test where the streambuf
  cannot hold a get area larger than an int (MSVC).
- unit-regression1: with JSON_NO_THREAD_LOCAL, copies are built member by
  member and keep correct timestamps, so check the corresponding outcome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp
Signed-off-by: Claude <noreply@anthropic.com>

* Return std::size_t from the hash helper in the ordered_json test

MSVC (Win32) rejected the implicit conversion of the unsigned long long
product to the 32-bit std::size_t (C4244 as an error). Also note when
JSON_NO_THREAD_LOCAL is set in the regression test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N5NasAgbDGG28MqXjQvLqp
Signed-off-by: Claude <noreply@anthropic.com>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Niels LohmannandClaude Opus 5.5 authored and GitHub committed 2026-11-02 17:54:38 +01:00
1 parent ff0bdfdef3
commit 1d69ef9ab4
55 files changed
+1892 -408

No files matched your search

+13 -13
View File
@@ -400,7 +400,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
#elif defined(__GNUC__) || defined(__GNUG__)
result["compiler"] = {{"family", "gcc"}, {"version", detail::concat<string_t>(
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC__)), '.',
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC__)), '.', // LCOV_EXCL_LINE
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_MINOR__)), '.',
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_PATCHLEVEL__)))
}
@@ -676,14 +676,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
case value_t::binary:
binary = create<binary_t>();
break;
case value_t::null:
case value_t::null: // LCOV_EXCL_LINE
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::discarded:
default:
break;
break; // LCOV_EXCL_LINE
}
}
@@ -1037,14 +1037,14 @@ public:
destroy_container(t);
break;
case value_t::null:
case value_t::null: // LCOV_EXCL_LINE
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::discarded:
default:
break;
break; // LCOV_EXCL_LINE
}
}
};
@@ -1217,9 +1217,9 @@ public:
return is_array() ? array_capacity() : object_capacity();
}
#else
static constexpr std::size_t container_capacity() noexcept
static constexpr std::size_t container_capacity() noexcept // LCOV_EXCL_LINE
{
return detail::unknown_size();
return detail::unknown_size(); // LCOV_EXCL_LINE
}
#endif
@@ -1405,7 +1405,7 @@ public:
/// into this switch is worth a few percent when copying a value made
/// mostly of numbers
JSON_HEDLEY_ALWAYS_INLINE
static void copy_leaf_value(const basic_json& src, basic_json& dst)
static void copy_leaf_value(const basic_json& src, basic_json& dst) // LCOV_EXCL_LINE
{
switch (src.m_data.m_type)
{
@@ -5589,7 +5589,7 @@ public:
/// their inlined code at each call site small. Binary values are
/// rare and left to equal_general().
JSON_HEDLEY_ALWAYS_INLINE
static int equal_leaf(const_reference lhs, const_reference rhs) noexcept
static int equal_leaf(const_reference lhs, const_reference rhs) noexcept // LCOV_EXCL_LINE
{
#ifdef __GNUC__
JSON_HEDLEY_DIAGNOSTIC_PUSH
@@ -6898,7 +6898,7 @@ public:
basic_json result(value_t::object);
json_pointer::flatten("", *this, result);
return result;
}
} // LCOV_EXCL_LINE (exception cleanup only)
/// @brief unflatten a previously flattened JSON value
/// @sa https://json.nlohmann.me/api/basic_json/unflatten/
@@ -7611,7 +7611,7 @@ public:
result.append(path.data(), path.size());
diff_append_key(result, key);
return result;
}
} // LCOV_EXCL_LINE (exception cleanup only)
/// @brief the path of the element @a index of the value at @a path
static string_t diff_index_path(const string_t& path, const std::size_t index)
@@ -7621,7 +7621,7 @@ public:
result.append(path.data(), path.size());
diff_append_index(result, index);
return result;
}
} // LCOV_EXCL_LINE (exception cleanup only)
/// @brief append the "remove" operations for the elements of array
/// @a source from @a index on, and the "add" operations for the
@@ -7781,7 +7781,7 @@ public:
// operations
if (source == target)
{
return false;
return false; // LCOV_EXCL_LINE (equal objects with the same members in the same order return early)
}
// slow path: the common keys are in a different relative