|
14 | 14 | #include <fcntl.h> |
15 | 15 | #include <unistd.h> |
16 | 16 | #include <math.h> /* for NAN */ |
| 17 | +#include <stdlib.h> /* for strtod */ |
17 | 18 |
|
18 | 19 |
|
19 | 20 | #include "flb_tests_internal.h" |
@@ -928,6 +929,130 @@ void test_json_pack_nan() |
928 | 929 | flb_pack_init(&config); |
929 | 930 | } |
930 | 931 |
|
| 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 | + |
931 | 1056 | static int check_msgpack_val(msgpack_object obj, int expected_type, char *expected_val) |
932 | 1057 | { |
933 | 1058 | int len; |
@@ -1303,6 +1428,8 @@ TEST_LIST = { |
1303 | 1428 | { "json_pack_bug342" , test_json_pack_bug342}, |
1304 | 1429 | { "json_pack_bug1278" , test_json_pack_bug1278}, |
1305 | 1430 | { "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}, |
1306 | 1433 | { "json_pack_bug5336" , test_json_pack_bug5336}, |
1307 | 1434 | { "json_pack_empty_array", test_json_pack_empty_array}, |
1308 | 1435 | { "json_date_iso8601" , test_json_date_iso8601}, |
|
0 commit comments