Skip to content

Commit 80b8a4a

Browse files
vitaly-sinevclaude
authored andcommitted
tests: internal: pack: add float round-trip formatting test
Verify that doubles are serialized to JSON using their shortest representation, e.g. 0.072 is emitted as "0.072" and not "0.07199999999999999", covering the regression from #6447. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: v.sinev <vit.phantom@gmail.com>
1 parent 3ace0b3 commit 80b8a4a

1 file changed

Lines changed: 127 additions & 0 deletions

File tree

tests/internal/pack.c

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <fcntl.h>
1515
#include <unistd.h>
1616
#include <math.h> /* for NAN */
17+
#include <stdlib.h> /* for strtod */
1718

1819

1920
#include "flb_tests_internal.h"
@@ -928,6 +929,130 @@ void test_json_pack_nan()
928929
flb_pack_init(&config);
929930
}
930931

932+
/*
933+
* https://github.com/fluent/fluent-bit/issues/6447
934+
* Floating point values must be serialized using their shortest
935+
* representation that round-trips back to the same double, so that e.g.
936+
* 0.072 is emitted as "0.072" and not "0.07199999999999999".
937+
*/
938+
void test_json_pack_float()
939+
{
940+
int i;
941+
int ret;
942+
char json_str[256];
943+
msgpack_sbuffer mp_sbuf;
944+
msgpack_packer mp_pck;
945+
msgpack_object obj;
946+
msgpack_zone mempool;
947+
struct float_case {
948+
double val;
949+
const char *expected;
950+
};
951+
struct float_case cases[] = {
952+
{ 0.072, "0.072" },
953+
{ 0.1, "0.1" },
954+
{ 3.14, "3.14" },
955+
{ 1.005, "1.005" },
956+
{ 5.0, "5.0" },
957+
{ -0.0, "-0.0" },
958+
};
959+
960+
for (i = 0; i < (int) (sizeof(cases) / sizeof(cases[0])); i++) {
961+
memset(json_str, 0, sizeof(json_str));
962+
963+
msgpack_sbuffer_init(&mp_sbuf);
964+
msgpack_packer_init(&mp_pck, &mp_sbuf, msgpack_sbuffer_write);
965+
msgpack_pack_double(&mp_pck, cases[i].val);
966+
msgpack_zone_init(&mempool, 2048);
967+
msgpack_unpack(mp_sbuf.data, mp_sbuf.size, NULL, &mempool, &obj);
968+
969+
ret = flb_msgpack_to_json(&json_str[0], sizeof(json_str), &obj,
970+
FLB_TRUE);
971+
TEST_CHECK(ret >= 0);
972+
973+
/* the serialized token must always parse back to the same value */
974+
if (!TEST_CHECK(strtod(json_str, NULL) == cases[i].val)) {
975+
TEST_MSG("value %.17g did not round-trip, got \"%s\"",
976+
cases[i].val, json_str);
977+
}
978+
#ifdef FLB_HAVE_YYJSON
979+
/* with the yyjson backend the shortest number token is emitted */
980+
if (!TEST_CHECK(strcmp(json_str, cases[i].expected) == 0)) {
981+
TEST_MSG("expected \"%s\", got \"%s\"",
982+
cases[i].expected, json_str);
983+
}
984+
#endif
985+
986+
msgpack_zone_destroy(&mempool);
987+
msgpack_sbuffer_destroy(&mp_sbuf);
988+
}
989+
}
990+
991+
/*
992+
* Direct coverage of flb_pack_double_to_str(), including the non-finite
993+
* fallback path, negatives, exponent and subnormal formatting, and the
994+
* undersized-buffer boundary.
995+
*/
996+
void test_pack_double_to_str()
997+
{
998+
int i;
999+
int len;
1000+
char buf[64];
1001+
double vals[] = {
1002+
0.072, -0.072, 0.1, 3.14, 1.005, 5.0, 100.0, 0.0, -0.0,
1003+
1e-7, 1e20, 1e-300, 1e300,
1004+
2.2250738585072014e-308, /* DBL_MIN */
1005+
4.9e-324 /* smallest subnormal */
1006+
};
1007+
1008+
/* every finite value must round-trip back to the same double */
1009+
for (i = 0; i < (int) (sizeof(vals) / sizeof(vals[0])); i++) {
1010+
len = flb_pack_double_to_str(vals[i], buf, sizeof(buf));
1011+
TEST_CHECK(len > 0);
1012+
TEST_CHECK((int) strlen(buf) == len);
1013+
if (!TEST_CHECK(strtod(buf, NULL) == vals[i])) {
1014+
TEST_MSG("value %.17g rendered as \"%s\" did not round-trip",
1015+
vals[i], buf);
1016+
}
1017+
}
1018+
1019+
#ifdef FLB_HAVE_YYJSON
1020+
/* known shortest representations (only with the yyjson backend) */
1021+
flb_pack_double_to_str(0.072, buf, sizeof(buf));
1022+
TEST_CHECK(strcmp(buf, "0.072") == 0);
1023+
flb_pack_double_to_str(5.0, buf, sizeof(buf));
1024+
TEST_CHECK(strcmp(buf, "5.0") == 0);
1025+
flb_pack_double_to_str(-0.0, buf, sizeof(buf));
1026+
TEST_CHECK(strcmp(buf, "-0.0") == 0);
1027+
#else
1028+
/* without yyjson, integer-valued doubles still keep the float marker */
1029+
flb_pack_double_to_str(5.0, buf, sizeof(buf));
1030+
TEST_CHECK(strcmp(buf, "5.0") == 0);
1031+
flb_pack_double_to_str(-0.0, buf, sizeof(buf));
1032+
TEST_CHECK(strcmp(buf, "-0.0") == 0);
1033+
#endif
1034+
1035+
/* non-finite values fall back to legacy formatting */
1036+
flb_pack_double_to_str(NAN, buf, sizeof(buf));
1037+
TEST_CHECK(strcmp(buf, "nan") == 0);
1038+
flb_pack_double_to_str(INFINITY, buf, sizeof(buf));
1039+
TEST_CHECK(strcmp(buf, "inf") == 0);
1040+
flb_pack_double_to_str(-INFINITY, buf, sizeof(buf));
1041+
TEST_CHECK(strcmp(buf, "-inf") == 0);
1042+
1043+
/* an undersized buffer must truncate safely, never overflow */
1044+
{
1045+
char small[4];
1046+
len = flb_pack_double_to_str(3.14159, small, sizeof(small));
1047+
TEST_CHECK(len >= 0 && len < (int) sizeof(small));
1048+
TEST_CHECK((int) strlen(small) == len);
1049+
}
1050+
1051+
/* invalid arguments must be rejected without touching memory */
1052+
TEST_CHECK(flb_pack_double_to_str(3.14, NULL, sizeof(buf)) == -1);
1053+
TEST_CHECK(flb_pack_double_to_str(3.14, buf, 0) == -1);
1054+
}
1055+
9311056
static int check_msgpack_val(msgpack_object obj, int expected_type, char *expected_val)
9321057
{
9331058
int len;
@@ -1303,6 +1428,8 @@ TEST_LIST = {
13031428
{ "json_pack_bug342" , test_json_pack_bug342},
13041429
{ "json_pack_bug1278" , test_json_pack_bug1278},
13051430
{ "json_pack_nan" , test_json_pack_nan},
1431+
{ "json_pack_float" , test_json_pack_float},
1432+
{ "pack_double_to_str" , test_pack_double_to_str},
13061433
{ "json_pack_bug5336" , test_json_pack_bug5336},
13071434
{ "json_pack_empty_array", test_json_pack_empty_array},
13081435
{ "json_date_iso8601" , test_json_date_iso8601},

0 commit comments

Comments
 (0)