mirror of
https://github.com/nlohmann/json.git
synced 2026-11-03 11:25:33 +00:00
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:
1 parent
ff0bdfdef3
commit
1d69ef9ab4
55 files changed
+1892
-408
No files matched your search
@@ -20,6 +20,7 @@ using nlohmann::ordered_json;
|
||||
using nlohmann::json_document;
|
||||
using nlohmann::json_editable_document;
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
@@ -448,6 +449,31 @@ TEST_CASE("json_view materialize() source positions")
|
||||
CHECK(same_positions(m[std::string("a") + '\n'][2], j[std::string("a") + '\n'][2]));
|
||||
}
|
||||
|
||||
SECTION("a damaged text: the end of a container is not found")
|
||||
{
|
||||
// (the text of an image is not checked with image_check::none)
|
||||
const std::string array_text = "[1,2]";
|
||||
const std::vector<std::uint8_t> original = json_document::parse(array_text).save();
|
||||
const auto at = std::search(original.begin(), original.end(), array_text.begin(), array_text.end());
|
||||
REQUIRE(at != original.end());
|
||||
const auto closing = static_cast<std::size_t>(at - original.begin()) + array_text.size() - 1;
|
||||
// not a closing bracket, a slash that starts no comment, and the end of the text
|
||||
for (const char replacement :
|
||||
{
|
||||
'x', '/', ' '
|
||||
})
|
||||
{
|
||||
CAPTURE(replacement)
|
||||
std::vector<std::uint8_t> damaged = original;
|
||||
damaged[closing] = static_cast<std::uint8_t>(replacement);
|
||||
const json_document d = json_document::load(damaged, json_document::image_check::none);
|
||||
const json m = d.root().materialize();
|
||||
CHECK(range(m) == "none");
|
||||
CHECK(range(m[0]) == "1-2");
|
||||
CHECK(range(m[1]) == "3-4");
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("a decoded string's position must be within the text")
|
||||
{
|
||||
@@ -482,6 +508,7 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
||||
{
|
||||
"null", "true", "42", "\"s\"", "[]", "{}", "[1, [2, [3, []]]]", R"({"a": {"b": [1, {"c": null}]}, "d": "e"})",
|
||||
R"({"a/b": {"~": [1]}, "\u0041\n": {"x": "\t"}, "": [{}]})",
|
||||
R"({"a\u0000b": [1, "x"], "c": {"d\u0000": 2}})", // (the path of a key ends at a null byte, as in basic_json)
|
||||
R"([1 /* ] */, {"a": [] // }
|
||||
, "b": {} } , ])",
|
||||
"\n\t[\r\n 1 ,\n {\n \"a\" : [ ]\n }\n]\n",
|
||||
@@ -533,6 +560,49 @@ TEST_CASE("json_view exceptions name the path and the bytes of the value")
|
||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"][2]["n"][0].get<int>()); }) == expected);
|
||||
}
|
||||
|
||||
SECTION("views of values that are no longer part of the document, and of no value")
|
||||
{
|
||||
const std::string expected_string = "[json.exception.type_error.302] type must be number, but is string";
|
||||
static_cast<void>(expected_string);
|
||||
// (a value that is not found has no path, and with it no bytes)
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse(R"(["x", ["y"]])");
|
||||
const auto removed = d.root()[0];
|
||||
d.erase(d.root(), 0);
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK(exception_of_call([&] { static_cast<void>(removed.get<int>()); }) == expected_string);
|
||||
#endif
|
||||
// (the root is a scalar now: the value cannot be in it)
|
||||
d.set(d.root(), 5);
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK(exception_of_call([&] { static_cast<void>(removed.get<int>()); }) == expected_string);
|
||||
#endif
|
||||
CHECK(d.root().get<int>() == 5);
|
||||
}
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse(R"(["x"])");
|
||||
const auto removed = d.root()[0];
|
||||
d.set(d.root(), 5);
|
||||
#if JSON_DIAGNOSTICS
|
||||
CHECK(exception_of_call([&] { static_cast<void>(removed.get<int>()); }) == expected_string);
|
||||
#endif
|
||||
}
|
||||
// a view of no value has no context
|
||||
CHECK(exception_of_call([&] { static_cast<void>(nlohmann::json_view().get<int>()); }) == "[json.exception.type_error.302] type must be number, but is discarded");
|
||||
}
|
||||
|
||||
SECTION("an array whose last element was replaced by a new container has no bytes")
|
||||
{
|
||||
json_editable_document d = json_editable_document::parse(R"({"a": [1, [2]], "b": 1})");
|
||||
d.set(d.root()["a"][1], json::array({3}));
|
||||
std::string expected = "[json.exception.type_error.302] ";
|
||||
#if JSON_DIAGNOSTICS
|
||||
expected += "(/a) ";
|
||||
#endif
|
||||
expected += "type must be number, but is array";
|
||||
CHECK(exception_of_call([&] { static_cast<void>(d.root()["a"].get<int>()); }) == expected);
|
||||
}
|
||||
|
||||
SECTION("deep documents with siblings after every nested container")
|
||||
{
|
||||
// the search for the path pushes and pops one frame per level (the
|
||||
|
||||
Reference in new issue
Block a user