From 86ed877c2c9c90c3c31755e454aeb78250e61895 Mon Sep 17 00:00:00 2001 From: Varun Deep Saini Date: Wed, 4 Mar 2026 11:54:51 +0530 Subject: [PATCH] MDEV-18318 Unit tests for the Json_writer Add 104 TAP unit tests for the Json_writer class covering: - Invalid JSON detection (unnamed values in objects, named values in arrays, mismatched open/close) - Output verification for all value types (strings, integers, unsigned integers, booleans, null, doubles, sizes) - Edge cases: LLONG_MAX/MIN, ULLONG_MAX, special doubles (0.0, -0.0), Latin-1 and UTF-8 mb4 encoded strings, embedded NUL bytes in keys and values, duplicate key names - add_member(name, len) explicit key length - Single-line formatting helper including long-element fallback and embedded-NUL disable behavior - Nested structures (objects in objects, arrays of objects, mixed) - RAII wrappers (Json_writer_object, Json_writer_array) including ulonglong add overload preserving unsigned range - String_with_limit truncation behavior - Json_writer size limit enforcement with exact truncation count - add_size formatting (bytes, Kb, Mb) with boundary tests - Positive-path test for add(name, value, num_bytes) overload Also fix a missing my_writer NULL guard in Json_writer_object::add(const char*, const char*, size_t) which would crash when used with a NULL writer. Harden Single_line_formatting_helper against embedded NUL bytes: on_add_member() now rejects keys containing NUL, and on_add_str() disables single-line mode and flushes when a value contains NUL. Signed-off-by: Varun Deep Saini --- sql/my_json_writer.cc | 9 +- sql/my_json_writer.h | 8 +- unittest/sql/my_json_writer-t.cc | 916 ++++++++++++++++++++++++++++++- 3 files changed, 919 insertions(+), 14 deletions(-) diff --git a/sql/my_json_writer.cc b/sql/my_json_writer.cc index b645d4e28b9f6..f2d0371479dcf 100644 --- a/sql/my_json_writer.cc +++ b/sql/my_json_writer.cc @@ -326,6 +326,8 @@ bool Single_line_formatting_helper::on_add_member(const char *name, size_t len) { DBUG_ASSERT(state== INACTIVE || state == DISABLED); + if (memchr(name, 0, len)) + return false; if (state != DISABLED) { // remove everything from the array @@ -389,6 +391,12 @@ bool Single_line_formatting_helper::on_add_str(const char *str, { if (state == IN_ARRAY) { + if (memchr(str, 0, len)) + { + disable_and_flush(); + return false; + } + // New length will be: // "$string", // quote + quote + comma + space = 4 @@ -485,4 +493,3 @@ void Single_line_formatting_helper::disable_and_flush() buf_ptr= buffer; state= INACTIVE; } - diff --git a/sql/my_json_writer.h b/sql/my_json_writer.h index 87d1a7facf15f..437c8ffbc0c62 100644 --- a/sql/my_json_writer.h +++ b/sql/my_json_writer.h @@ -523,8 +523,12 @@ class Json_writer_object : public Json_writer_struct } Json_writer_object& add(const char *name, const char *value, size_t num_bytes) { - add_member(name); - context.add_str(value, num_bytes); + DBUG_ASSERT(!closed); + if (my_writer) + { + add_member(name); + context.add_str(value, num_bytes); + } return *this; } Json_writer_object& add(const char *name, const LEX_CSTRING &value) diff --git a/unittest/sql/my_json_writer-t.cc b/unittest/sql/my_json_writer-t.cc index 8d8fda3f4c90b..e22009b885a4f 100644 --- a/unittest/sql/my_json_writer-t.cc +++ b/unittest/sql/my_json_writer-t.cc @@ -21,8 +21,7 @@ #include /* - Unit tests for class Json_writer. At the moment there are only tests for the - "Fail an assertion if one attempts to produce invalid JSON" feature. + Unit tests for class Json_writer. */ struct TABLE; @@ -30,7 +29,7 @@ class Json_writer; /* Several fake objects */ -class Opt_trace +class Opt_trace { public: void enable_tracing_if_required() {} @@ -38,7 +37,7 @@ class Opt_trace Json_writer *get_current_json() { return nullptr; } }; -class THD +class THD { public: Opt_trace opt_trace; @@ -52,17 +51,56 @@ constexpr uint FAKE_SELECT_LEX_ID= UINT_MAX; #include "../sql/my_json_writer.h" #include "../sql/my_json_writer.cc" +static bool json_output_eq(Json_writer &w, const char *expected) +{ + const String *s= w.output.get_string(); + size_t exp_len= strlen(expected); + if (s->length() == exp_len && memcmp(s->ptr(), expected, exp_len) == 0) + return true; + diag(" Expected (%d bytes): [%s]", (int) exp_len, expected); + diag(" Actual (%d bytes): [%.*s]", + (int) s->length(), (int) s->length(), s->ptr()); + return false; +} + +static bool json_output_eq_len(Json_writer &w, + const char *expected, + size_t expected_len) +{ + const String *s= w.output.get_string(); + if (s->length() == expected_len && + memcmp(s->ptr(), expected, expected_len) == 0) + return true; + + diag(" Expected length: %d bytes", (int) expected_len); + diag(" Actual length : %d bytes", (int) s->length()); + + size_t common_len= s->length() < expected_len ? s->length() : expected_len; + for (size_t i= 0; i < common_len; i++) + { + uchar actual= (uchar) s->ptr()[i]; + uchar exp= (uchar) expected[i]; + if (actual != exp) + { + diag(" First mismatch at byte %d: expected 0x%02x, actual 0x%02x", + (int) i, (uint) exp, (uint) actual); + break; + } + } + return false; +} + int main(int args, char **argv) { MY_INIT(argv[0]); - plan(NO_PLAN); + plan(104); diag("Testing Json_writer checks"); { Json_writer w; w.start_object(); - w.add_member("foo"); + w.add_member("foo"); w.end_object(); ok(w.invalid_json, "Started a name but didn't add a value"); } @@ -127,18 +165,875 @@ int main(int args, char **argv) { Json_writer w; w.start_object(); - w.add_member("name").add_ll(1); + w.add_member("name").start_object(); w.add_member("name").add_ll(2); w.end_object(); - ok(w.invalid_json, "JSON object member name collision"); + w.end_object(); + ok(!w.invalid_json, "Valid JSON: nested object member name is the same"); + ok(json_output_eq(w, + "{\n" + " \"name\": {\n" + " \"name\": 2\n" + " }\n" + "}"), + "Nested same-name key output"); } + diag("Testing Json_writer output"); + { Json_writer w; w.start_object(); - w.add_member("name").start_object(); + w.end_object(); + ok(json_output_eq(w, "{}"), "Empty object"); + ok(!w.invalid_json, "Empty object is valid"); + } + + { + Json_writer w; + w.start_array(); + w.end_array(); + ok(json_output_eq(w, "[]"), "Empty array"); + ok(!w.invalid_json, "Empty array is valid"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("key").add_str("hello"); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"key\": \"hello\"\n" + "}"), + "String value"); + ok(!w.invalid_json, "String value is valid"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("s").add_str(""); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"s\": \"\"\n" + "}"), + "Empty string value"); + } + + /* add_str does not escape: special chars are passed through verbatim */ + { + Json_writer w; + w.start_object(); + w.add_member("s").add_str("with\"quote"); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"s\": \"with\"quote\"\n" + "}"), + "String with embedded quote (no escaping)"); + } + + { + Json_writer w; + String latin1_str("\xe9\xf6\xfc", 3, &my_charset_latin1); + w.start_object(); + w.add_member("s").add_str(latin1_str); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"s\": \"\xe9\xf6\xfc\"\n" + "}"), + "Latin-1 encoded string bytes pass through unchanged"); + } + + { + Json_writer w; + /* 4-byte UTF-8 sequence (U+1F600) */ + String utf8mb4_str("\xf0\x9f\x98\x80", 4, &my_charset_utf8mb4_general_ci); + w.start_object(); + w.add_member("s").add_str(utf8mb4_str); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"s\": \"\xf0\x9f\x98\x80\"\n" + "}"), + "UTF-8 mb4 encoded string bytes pass through unchanged"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("num").add_ll(42); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"num\": 42\n" + "}"), + "Positive longlong value"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("num").add_ll(-100); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"num\": -100\n" + "}"), + "Negative longlong value"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("num").add_ll(LLONG_MAX); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"num\": 9223372036854775807\n" + "}"), + "LLONG_MAX"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("num").add_ll(LLONG_MIN); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"num\": -9223372036854775808\n" + "}"), + "LLONG_MIN"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("num").add_ull(ULLONG_MAX); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"num\": 18446744073709551615\n" + "}"), + "ULLONG_MAX"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("flag").add_bool(true); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"flag\": true\n" + "}"), + "Bool true"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("val").add_null(); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"val\": null\n" + "}"), + "Null value"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("pi").add_double(3.14); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"pi\": 3.14\n" + "}"), + "Double value"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("d").add_double(0.0); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"d\": 0\n" + "}"), + "Double zero"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("d").add_double(-0.0); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"d\": -0\n" + "}"), + "Double negative zero"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("d").add_double(1e-10); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"d\": 1e-10\n" + "}"), + "Double very small value"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("d").add_double(1e+15); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"d\": 1e15\n" + "}"), + "Double very large value"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("a").add_str("hello"); + w.add_member("b").add_ll(42); + w.add_member("c").add_bool(true); + w.add_member("d").add_null(); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"a\": \"hello\",\n" + " \"b\": 42,\n" + " \"c\": true,\n" + " \"d\": null\n" + "}"), + "Object with multiple typed members"); + ok(!w.invalid_json, "Multiple members is valid"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("key with spaces").add_ll(1); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"key with spaces\": 1\n" + "}"), + "Key with spaces"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("cl\xe9").add_ll(1); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"cl\xe9\": 1\n" + "}"), + "Key with Latin-1 byte"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("has\"quote").add_ll(1); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"has\"quote\": 1\n" + "}"), + "Key with embedded quote (no escaping)"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("").add_ll(1); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"\": 1\n" + "}"), + "Empty key name"); + } + + { + Json_writer w; + const char key[]= "prefix"; + w.start_object(); + w.add_member(key, 3).add_ll(7); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"pre\": 7\n" + "}"), + "add_member(name, len) uses explicit key length"); + ok(!w.invalid_json, "add_member(name, len) remains valid"); + } + + { + Json_writer w; + const char key_with_nul[]= { 'k', '\0', 'y' }; + const char expected[]= + "{\n" + " \"k\0y\": 1\n" + "}"; + w.start_object(); + w.add_member(key_with_nul, sizeof(key_with_nul)).add_ll(1); + w.end_object(); + ok(json_output_eq_len(w, expected, sizeof(expected) - 1), + "add_member(name, len) preserves embedded NUL in key"); + ok(!w.invalid_json, "Embedded-NUL key does not mark writer invalid"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("name").add_ll(1); w.add_member("name").add_ll(2); - ok(!w.invalid_json, "Valid JSON: nested object member name is the same"); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"name\": 1,\n" + " \"name\": 2\n" + "}"), + "Duplicate key output"); + ok(w.invalid_json, "Duplicate key is invalid JSON"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("arr"); + w.start_array(); + w.add_str("a"); + w.add_str("b"); + w.add_str("c"); + w.end_array(); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"arr\": [\"a\", \"b\", \"c\"]\n" + "}"), + "Named string array: single-line format"); + ok(!w.invalid_json, "Named string array is valid"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("one"); + w.start_array(); + w.add_str("only"); + w.end_array(); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"one\": [\"only\"]\n" + "}"), + "Named array with single element"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("empty"); + w.start_array(); + w.end_array(); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"empty\": []\n" + "}"), + "Empty named array"); + ok(!w.invalid_json, "Empty named array is valid"); + } + + { + Json_writer w; + const char value_with_nul[]= { 'a', '\0', 'b' }; + const char expected[]= + "{\n" + " \"arr\": [\n" + " \"a\0b\"\n" + " ]\n" + "}"; + w.start_object(); + w.add_member("arr"); + w.start_array(); + w.add_str(value_with_nul, sizeof(value_with_nul)); + w.end_array(); + w.end_object(); + ok(json_output_eq_len(w, expected, sizeof(expected) - 1), + "Named array element with embedded NUL is preserved"); + ok(!w.invalid_json, "Embedded-NUL array element keeps writer valid"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("arr"); + w.start_array(); + w.add_str("first"); + w.start_object(); + w.add_member("k").add_ll(1); + w.end_object(); + w.end_array(); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"arr\": [\n" + " \"first\",\n" + " {\n" + " \"k\": 1\n" + " }\n" + " ]\n" + "}"), + "Single-line helper flushes buffered values before nested object"); + ok(!w.invalid_json, "Nested object in named array stays valid"); + } + + { + Json_writer w; + char long_val[91]; + memset(long_val, 'b', 90); + long_val[90]= 0; + w.start_object(); + w.add_member("arr"); + w.start_array(); + w.add_str("a"); + w.add_str(long_val); + w.end_array(); + w.end_object(); + + String expected; + expected.append(STRING_WITH_LEN("{\n" + " \"arr\": [\n" + " \"a\",\n" + " \"")); + expected.append(long_val, 90); + expected.append(STRING_WITH_LEN("\"\n" + " ]\n" + "}")); + ok(json_output_eq_len(w, expected.ptr(), expected.length()), + "Single-line helper falls back when line length exceeds threshold"); + ok(!w.invalid_json, "Long element fallback output stays valid"); + } + + { + Json_writer w; + w.start_array(); + w.add_str("x"); + w.add_str("y"); + w.end_array(); + ok(json_output_eq(w, + "[\n" + " \"x\",\n" + " \"y\"\n" + "]"), + "Root array: multi-line format"); + ok(!w.invalid_json, "Root array is valid"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("outer").start_object(); + w.add_member("inner").add_str("val"); + w.end_object(); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"outer\": {\n" + " \"inner\": \"val\"\n" + " }\n" + "}"), + "Nested objects"); + ok(!w.invalid_json, "Nested objects valid"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("items"); + w.start_array(); + w.start_object(); + w.add_member("x").add_ll(10); + w.end_object(); + w.start_object(); + w.add_member("x").add_ll(20); + w.end_object(); + w.end_array(); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"items\": [\n" + " {\n" + " \"x\": 10\n" + " },\n" + " {\n" + " \"x\": 20\n" + " }\n" + " ]\n" + "}"), + "Object with array of objects"); + ok(!w.invalid_json, "Object with array of objects valid"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("name").add_str("test"); + w.add_member("enabled").add_bool(true); + w.add_member("config").start_object(); + w.add_member("timeout").add_ll(30); + w.add_member("tags"); + w.start_array(); + w.add_str("fast"); + w.add_str("reliable"); + w.end_array(); + w.end_object(); + w.add_member("count").add_ll(0); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"name\": \"test\",\n" + " \"enabled\": true,\n" + " \"config\": {\n" + " \"timeout\": 30,\n" + " \"tags\": [\"fast\", \"reliable\"]\n" + " },\n" + " \"count\": 0\n" + "}"), + "Complex mixed structure"); + ok(!w.invalid_json, "Complex structure valid"); + } + + diag("Testing RAII wrappers"); + + { + Json_writer w; + { + Json_writer_object obj(&w); + w.add_member("key").add_str("val"); + } + ok(json_output_eq(w, + "{\n" + " \"key\": \"val\"\n" + "}"), + "Json_writer_object auto-closes on destruction"); + ok(!w.invalid_json, "RAII object valid"); + } + + { + Json_writer w; + { + Json_writer_object obj(&w); + obj.add("name", "test"); + obj.add("count", (longlong) 7); + obj.add("flag", true); + } + ok(json_output_eq(w, + "{\n" + " \"name\": \"test\",\n" + " \"count\": 7,\n" + " \"flag\": true\n" + "}"), + "Json_writer_object fluent add()"); + ok(!w.invalid_json, "Fluent add valid"); + } + + { + Json_writer w; + { + Json_writer_object obj(&w); + obj.add("u", (ulonglong) ULLONG_MAX); + } + ok(json_output_eq(w, + "{\n" + " \"u\": 18446744073709551615\n" + "}"), + "Json_writer_object add(ulonglong) preserves unsigned range"); + ok(!w.invalid_json, "Json_writer_object add(ulonglong) stays valid"); + } + + { + Json_writer w; + Json_writer_object obj(&w); + obj.add("a", (longlong) 1); + obj.end(); + ok(json_output_eq(w, + "{\n" + " \"a\": 1\n" + "}"), + "Json_writer_object explicit end()"); + ok(!w.invalid_json, "Explicit end valid"); + } + + { + Json_writer w; + { + Json_writer_array arr(&w); + arr.add("x"); + arr.end(); + } + ok(json_output_eq(w, + "[\n" + " \"x\"\n" + "]"), + "Json_writer_array explicit end()"); + ok(!w.invalid_json, "Json_writer_array explicit end stays valid"); + } + + { + Json_writer w; + { + Json_writer_array arr(&w); + arr.add("foo"); + arr.add("bar"); + } + ok(json_output_eq(w, + "[\n" + " \"foo\",\n" + " \"bar\"\n" + "]"), + "Json_writer_array auto-closes on destruction"); + ok(!w.invalid_json, "RAII array valid"); + } + + { + Json_writer w; + { + Json_writer_array arr(&w); + arr.add((ulonglong) 42); + } + ok(json_output_eq(w, + "[\n" + " 42\n" + "]"), + "RAII array add(ulonglong) output"); + ok(!w.invalid_json, "RAII array add(ulonglong) valid"); + } + + { + Json_writer w; + { + Json_writer_object outer(&w); + outer.add("level", "outer"); + { + Json_writer_object inner(&w, "nested"); + inner.add("level", "inner"); + } + } + ok(json_output_eq(w, + "{\n" + " \"level\": \"outer\",\n" + " \"nested\": {\n" + " \"level\": \"inner\"\n" + " }\n" + "}"), + "Nested RAII Json_writer_objects"); + ok(!w.invalid_json, "Nested RAII valid"); + } + + { + Json_writer w; + { + Json_writer_object obj(&w); + obj.add("name", "test"); + { + Json_writer_array arr(&w, "values"); + arr.add("a"); + arr.add("b"); + } + } + ok(json_output_eq(w, + "{\n" + " \"name\": \"test\",\n" + " \"values\": [\"a\", \"b\"]\n" + "}"), + "RAII array inside RAII object"); + ok(!w.invalid_json, "RAII array in object valid"); + } + + { + Json_writer w; + { + Json_writer_object obj(&w); + obj.add("partial", "hello world", 5); + } + ok(json_output_eq(w, + "{\n" + " \"partial\": \"hello\"\n" + "}"), + "add(name, value, num_bytes) honors length"); + ok(!w.invalid_json, + "add(name, value, num_bytes) produces valid JSON"); + } + + diag("Testing String_with_limit"); + + { + String_with_limit sl; + sl.append("hello"); + sl.append(" world"); + ok(sl.length() == 11, "Normal append: length == 11"); + ok(sl.get_truncated_bytes() == 0, "Normal append: no truncation"); + const String *s= sl.get_string(); + ok(s->length() == 11 && memcmp(s->ptr(), "hello world", 11) == 0, + "Normal append: content correct"); + } + + { + String_with_limit sl; + sl.set_size_limit(5); + sl.append("hello world"); + ok(sl.length() == 5, "Truncation: length capped at 5"); + ok(sl.get_truncated_bytes() == 6, "Truncation: 6 bytes truncated"); + const String *s= sl.get_string(); + ok(s->length() == 5 && memcmp(s->ptr(), "hello", 5) == 0, + "Truncation: content is prefix"); + } + + { + String_with_limit sl; + sl.set_size_limit(5); + sl.append("hi"); + sl.append(" there buddy"); + ok(sl.length() == 5, "Multi-append truncation: length == 5"); + ok(sl.get_truncated_bytes() == 9, "Multi-append truncation: 9 truncated"); + const String *s= sl.get_string(); + ok(s->length() == 5 && memcmp(s->ptr(), "hi th", 5) == 0, + "Multi-append truncation: correct partial content"); + } + + { + String_with_limit sl; + sl.set_size_limit(3); + sl.append("abc"); + sl.append("def"); + ok(sl.length() == 3, "At-limit overflow: length == 3"); + ok(sl.get_truncated_bytes() == 3, "At-limit overflow: 3 truncated"); + } + + { + String_with_limit sl; + sl.set_size_limit(3); + sl.append('a'); + sl.append('b'); + sl.append('c'); + sl.append('d'); + ok(sl.length() == 3, "Char append truncation: length == 3"); + ok(sl.get_truncated_bytes() == 1, "Char append truncation: 1 truncated"); + } + + diag("Testing Json_writer size limit"); + + { + Json_writer full; + full.start_object(); + full.add_member("longkey").add_str("longvalue"); + full.end_object(); + + Json_writer limited; + limited.set_size_limit(10); + limited.start_object(); + limited.add_member("longkey").add_str("longvalue"); + limited.end_object(); + + ok(limited.output.length() == 10, "Size limit enforced on Json_writer"); + ok(limited.get_truncated_bytes() == + full.output.length() - limited.output.length(), + "Truncated byte count is exact"); + } + + diag("Testing add_size formatting"); + + { + Json_writer w; + w.start_object(); + w.add_member("size").add_size(512); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"size\": \"512\"\n" + "}"), + "add_size: bytes (< 1024)"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("size").add_size(2048); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"size\": \"2Kb\"\n" + "}"), + "add_size: Kb value"); + } + + { + Json_writer w; + w.start_object(); + w.add_member("size").add_size((longlong) 32 * 1024 * 1024); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"size\": \"32Mb\"\n" + "}"), + "add_size: Mb value"); + } + + { + Json_writer w; + const longlong mb16= (longlong) 16 * 1024 * 1024; + w.start_object(); + w.add_member("below_kb").add_size(1023); + w.add_member("at_kb").add_size(1024); + w.add_member("below_16mb").add_size(mb16 - 1); + w.add_member("at_16mb").add_size(mb16); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"below_kb\": \"1023\",\n" + " \"at_kb\": \"1Kb\",\n" + " \"below_16mb\": \"16383Kb\",\n" + " \"at_16mb\": \"16Mb\"\n" + "}"), + "add_size boundary formatting"); + } + + diag("Testing add_str overloads"); + + { + Json_writer w; + String s; + s.append("hello", 5); + w.start_object(); + w.add_member("msg").add_str(s); + w.end_object(); + ok(json_output_eq(w, + "{\n" + " \"msg\": \"hello\"\n" + "}"), + "add_str with String object"); } diag("Done"); @@ -146,4 +1041,3 @@ int main(int args, char **argv) my_end(MY_CHECK_ERROR); return exit_status(); } -