mirror of
https://github.com/nlohmann/json.git
synced 2026-11-03 03:15:34 +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
@@ -495,3 +495,206 @@ TEST_CASE("locale changes during a single dump() (#5709 item 3)")
|
||||
// fix #5597 made on the parser side
|
||||
CHECK(buf.data == "[\"" + padding + "\",1234.5]");
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// a token whose first data() call switches LC_NUMERIC, so that the locale
|
||||
// changes between convert_float_locale_aware() looking up the decimal point
|
||||
// and strtod reading the token (the race of #5198, made deterministic)
|
||||
struct locale_switching_token
|
||||
{
|
||||
using value_type = char;
|
||||
|
||||
locale_switching_token(std::string text_, const char* switch_to_)
|
||||
: text(std::move(text_)), switch_to(switch_to_)
|
||||
{}
|
||||
|
||||
std::size_t size() const
|
||||
{
|
||||
return text.size();
|
||||
}
|
||||
|
||||
char& operator[](std::size_t i)
|
||||
{
|
||||
return text[i];
|
||||
}
|
||||
|
||||
char* data()
|
||||
{
|
||||
if (!switched)
|
||||
{
|
||||
switched = std::setlocale(LC_NUMERIC, switch_to) != nullptr;
|
||||
}
|
||||
return &text[0];
|
||||
}
|
||||
|
||||
std::string text;
|
||||
const char* switch_to;
|
||||
bool switched = false;
|
||||
};
|
||||
|
||||
// set LC_NUMERIC to the first of @a names that is usable and has a decimal
|
||||
// point of @a min_size bytes at least (and at most @a max_size)
|
||||
bool use_decimal_point_locale(const std::vector<const char*>& names, std::size_t min_size, std::size_t max_size, const char*& used)
|
||||
{
|
||||
for (const char* name : names)
|
||||
{
|
||||
if (std::setlocale(LC_NUMERIC, name) == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const std::size_t size = std::string(std::localeconv()->decimal_point).size();
|
||||
if (size >= min_size && size <= max_size)
|
||||
{
|
||||
used = name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// convert a token with convert_float_locale_aware(); the token must be given
|
||||
// back as it was
|
||||
template<typename FloatType>
|
||||
FloatType convert_locale_aware(const std::string& text)
|
||||
{
|
||||
std::string token = text;
|
||||
const std::size_t dot = token.find('.');
|
||||
FloatType value{};
|
||||
nlohmann::detail::convert_float_locale_aware(token, dot, value);
|
||||
CHECK(token == text);
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename FloatType>
|
||||
void check_locale_aware_conversions()
|
||||
{
|
||||
CHECK(convert_locale_aware<FloatType>("3.5") == FloatType(3.5));
|
||||
CHECK(convert_locale_aware<FloatType>("-0.25") == FloatType(-0.25));
|
||||
CHECK(convert_locale_aware<FloatType>("1.25e2") == FloatType(125));
|
||||
// no decimal point at all
|
||||
CHECK(convert_locale_aware<FloatType>("12e1") == FloatType(120));
|
||||
CHECK(convert_locale_aware<FloatType>("7") == FloatType(7));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("convert_float_locale_aware")
|
||||
{
|
||||
const std::vector<const char*> single_byte_names = {"de_DE.UTF-8", "de_DE", "fr_FR.UTF-8", "fr_FR", "ru_RU.UTF-8"};
|
||||
const std::vector<const char*> multi_byte_names = {"ar_EG.UTF-8", "ar_SA.UTF-8", "fa_IR.UTF-8", "ps_AF.UTF-8", "ar_EG", "fa_IR"};
|
||||
const char* used = nullptr;
|
||||
|
||||
SECTION("decimal point '.'")
|
||||
{
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
check_locale_aware_conversions<float>();
|
||||
check_locale_aware_conversions<double>();
|
||||
check_locale_aware_conversions<long double>();
|
||||
}
|
||||
|
||||
SECTION("single-byte decimal point other than '.'")
|
||||
{
|
||||
if (use_decimal_point_locale(single_byte_names, 1, 1, used) && std::localeconv()->decimal_point[0] != '.')
|
||||
{
|
||||
CAPTURE(used)
|
||||
check_locale_aware_conversions<float>();
|
||||
check_locale_aware_conversions<double>();
|
||||
check_locale_aware_conversions<long double>();
|
||||
}
|
||||
else
|
||||
{
|
||||
MESSAGE("no locale with a single-byte decimal point other than '.' is usable");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("multi-byte decimal point")
|
||||
{
|
||||
if (use_decimal_point_locale(multi_byte_names, 2, 8, used))
|
||||
{
|
||||
CAPTURE(used)
|
||||
check_locale_aware_conversions<float>();
|
||||
check_locale_aware_conversions<double>();
|
||||
check_locale_aware_conversions<long double>();
|
||||
}
|
||||
else
|
||||
{
|
||||
MESSAGE("no locale with a multi-byte decimal point is usable");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a token that is not converted completely")
|
||||
{
|
||||
// the locale does not change, so there is nothing to retry: the
|
||||
// value up to the garbage is kept
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
std::string token = "1.5x";
|
||||
double value = 0;
|
||||
nlohmann::detail::convert_float_locale_aware(token, token.find('.'), value);
|
||||
CHECK(value == 1.5);
|
||||
CHECK(token == "1.5x");
|
||||
}
|
||||
|
||||
SECTION("the locale changes before the conversion")
|
||||
{
|
||||
if (!use_decimal_point_locale(single_byte_names, 1, 1, used) || std::localeconv()->decimal_point[0] == '.')
|
||||
{
|
||||
MESSAGE("no locale with a single-byte decimal point other than '.' is usable");
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string comma_locale = used;
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
|
||||
// "C" -> comma locale: strtod stops at the '.', so the conversion is repeated
|
||||
{
|
||||
locale_switching_token token("1.5", comma_locale.c_str());
|
||||
double value = 0;
|
||||
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||
if (token.switched)
|
||||
{
|
||||
CHECK(value == 1.5);
|
||||
CHECK(token.text == "1.5");
|
||||
}
|
||||
}
|
||||
|
||||
// the same for the other floating-point types
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
{
|
||||
locale_switching_token token("2.25", comma_locale.c_str());
|
||||
float value = 0;
|
||||
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||
if (token.switched)
|
||||
{
|
||||
CHECK(value == 2.25f);
|
||||
}
|
||||
}
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
{
|
||||
locale_switching_token token("2.25", comma_locale.c_str());
|
||||
long double value = 0;
|
||||
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||
if (token.switched)
|
||||
{
|
||||
CHECK(value == 2.25L);
|
||||
}
|
||||
}
|
||||
|
||||
// comma locale -> multi-byte decimal point
|
||||
const char* multi_byte = nullptr;
|
||||
if (use_decimal_point_locale(multi_byte_names, 2, 8, multi_byte))
|
||||
{
|
||||
REQUIRE(std::setlocale(LC_NUMERIC, comma_locale.c_str()) != nullptr);
|
||||
locale_switching_token token("1.5", multi_byte);
|
||||
double value = 0;
|
||||
nlohmann::detail::convert_float_locale_aware(token, 1, value);
|
||||
if (token.switched)
|
||||
{
|
||||
CHECK(value == 1.5);
|
||||
CHECK(token.text == "1.5");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(std::setlocale(LC_NUMERIC, "C") != nullptr);
|
||||
}
|
||||
Reference in new issue
Block a user