mirror of
https://github.com/nlohmann/json.git
synced 2026-11-04 11:49:22 +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
@@ -14,9 +14,11 @@ using nlohmann::json;
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "make_test_data_available.hpp"
|
||||
|
||||
@@ -2453,3 +2455,99 @@ TEST_CASE("JSON patch - diff behavior documented in the API reference")
|
||||
CHECK(json::diff(json(nullptr), json::array()) == R"([{"op": "replace", "path": "", "value": []}])"_json);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch - operations that are left to json_pointer")
|
||||
{
|
||||
// the usual operations on a plain pointer are applied without building a
|
||||
// json_pointer; the following are not plain, and some are errors
|
||||
SECTION("a path without a slash")
|
||||
{
|
||||
const json j = {{"a", 1}};
|
||||
|
||||
CHECK_THROWS_AS(j.patch(R"([{"op": "add", "path": "abc", "value": 1}])"_json), json::parse_error&);
|
||||
CHECK_THROWS_AS(j.patch(R"([{"op": "remove", "path": "abc"}])"_json), json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("remove with an escaped reference token")
|
||||
{
|
||||
const json j = {{"a/b", 1}, {"c~d", 2}, {"e", 3}};
|
||||
|
||||
CHECK(j.patch(R"([{"op": "remove", "path": "/a~1b"}])"_json) == json({{"c~d", 2}, {"e", 3}}));
|
||||
CHECK(j.patch(R"([{"op": "remove", "path": "/c~0d"}])"_json) == json({{"a/b", 1}, {"e", 3}}));
|
||||
CHECK_THROWS_AS(j.patch(R"([{"op": "remove", "path": "/missing~1key"}])"_json), json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("a path through a primitive value")
|
||||
{
|
||||
const json j = {{"a", 1}};
|
||||
|
||||
CHECK_THROWS_AS(j.patch(R"([{"op": "test", "path": "/a/b", "value": 1}])"_json), json::other_error&);
|
||||
CHECK_THROWS_AS(j.patch(R"([{"op": "replace", "path": "/a/b", "value": 1}])"_json), json::out_of_range&);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch - diff of objects with many members")
|
||||
{
|
||||
// more members than diff() keeps without allocating
|
||||
for (const std::size_t members :
|
||||
{
|
||||
16u, 17u, 40u
|
||||
})
|
||||
{
|
||||
CAPTURE(members)
|
||||
|
||||
json source = json::object();
|
||||
json target = json::object();
|
||||
for (std::size_t i = 0; i < members; ++i)
|
||||
{
|
||||
const std::string key = "key" + std::to_string(i);
|
||||
source[key] = i;
|
||||
// every third member differs
|
||||
target[key] = (i % 3 == 0) ? i + 1 : i;
|
||||
}
|
||||
// the same keys, a removed and an added one
|
||||
source["removed"] = true;
|
||||
target["added"] = false;
|
||||
|
||||
const json patch = json::diff(source, target);
|
||||
CHECK(source.patch(patch) == target);
|
||||
CHECK(patch.size() == ((members + 2) / 3) + 2);
|
||||
|
||||
const nlohmann::ordered_json ordered_source = source;
|
||||
const nlohmann::ordered_json ordered_target = target;
|
||||
CHECK(ordered_source.patch(nlohmann::ordered_json::diff(ordered_source, ordered_target)) == ordered_target);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch - diff of leaf values below the bound of the recursive descent")
|
||||
{
|
||||
// the values at the innermost level are diffed without recursion: equal
|
||||
// leaves yield nothing, leaves of different types and different leaves
|
||||
// are replaced
|
||||
const std::size_t depth = 200;
|
||||
|
||||
json source = {1, "text", 3, {{"x", 1}, {"y", 2}}, true, nullptr};
|
||||
json target = {1, 2, 4, {{"x", 1}, {"y", 3}}, true, nullptr};
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
source = json::array({std::move(source)});
|
||||
target = json::array({std::move(target)});
|
||||
}
|
||||
|
||||
const json patch = json::diff(source, target);
|
||||
CHECK(patch.size() == 3);
|
||||
CHECK(source.patch(patch) == target);
|
||||
|
||||
// the same with objects as the outer levels
|
||||
json object_source = {{"equal", 1}, {"type", "text"}, {"value", 3}};
|
||||
json object_target = {{"equal", 1}, {"type", 2}, {"value", 4}};
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
object_source = json::object({{"a", std::move(object_source)}});
|
||||
object_target = json::object({{"a", std::move(object_target)}});
|
||||
}
|
||||
|
||||
const json object_patch = json::diff(object_source, object_target);
|
||||
CHECK(object_patch.size() == 2);
|
||||
CHECK(object_source.patch(object_patch) == object_target);
|
||||
}
|
||||
Reference in new issue
Block a user