1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

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 <varun.23bcs10048@ms.sst.scaler.com>
Signed-off-by: Varun Deep Saini <deepsainivarun@gmail.com>
This commit is contained in:
Varun Deep Saini
2026-03-04 11:54:51 +05:30
committed by Daniel Black
parent 765cb456a0
commit e7bd8976f6
11 changed files with 973 additions and 38 deletions

View File

@@ -188,7 +188,7 @@ ANALYZE
"attached_condition": "tbl2.b < 60"
},
"buffer_type": "flat",
"buffer_size": "1Kb",
"buffer_size": "1KiB",
"join_type": "BNL",
"r_loops": 20,
"r_filtered": 100,
@@ -243,7 +243,7 @@ ANALYZE
"attached_condition": "tbl2.b < 60"
},
"buffer_type": "flat",
"buffer_size": "1Kb",
"buffer_size": "1KiB",
"join_type": "BNL",
"attached_condition": "tbl1.c > tbl2.c",
"r_loops": 20,
@@ -1127,7 +1127,7 @@ ANALYZE
"attached_condition": "t10.a < 700"
},
"buffer_type": "flat",
"buffer_size": "1Kb",
"buffer_size": "1KiB",
"join_type": "BNL",
"attached_condition": "t10.b = t11.b",
"r_loops": 100,
@@ -1192,7 +1192,7 @@ ANALYZE
"attached_condition": "t10.a < 700"
},
"buffer_type": "flat",
"buffer_size": "3Kb",
"buffer_size": "3KiB",
"join_type": "BNLH",
"attached_condition": "t10.b = t11.b",
"r_loops": 100,

View File

@@ -80,7 +80,7 @@ X
"attached_condition": "t2.a < 100"
},
"buffer_type": "flat",
"buffer_size": "18Kb",
"buffer_size": "18KiB",
"join_type": "BNLH",
"attached_condition": "t2.b = t1.b",
"r_loops": 500,
@@ -172,7 +172,7 @@ X
"r_filtered": 100
},
"buffer_type": "flat",
"buffer_size": "256Kb",
"buffer_size": "256KiB",
"join_type": "BNLH",
"attached_condition": "t2.a = t1.a and concat(t1.col1,t1.col2,t1.col3) = concat(t2.col1,t2.col2,t2.col3)",
"r_loops": 1000,

View File

@@ -3510,7 +3510,7 @@ EXPLAIN
"filtered": 100
},
"buffer_type": "incremental",
"buffer_size": "4Kb",
"buffer_size": "4KiB",
"join_type": "BNL",
"attached_condition": "v1.a = t2.a and v2.a = t2.a or v2.b > 13 and t2.c < 115",
"materialized": {
@@ -3626,7 +3626,7 @@ EXPLAIN
"attached_condition": "v2.b < 50 or v2.b = 19"
},
"buffer_type": "incremental",
"buffer_size": "4Kb",
"buffer_size": "4KiB",
"join_type": "BNL",
"attached_condition": "(v2.a = v1.a or v1.a = t2.a) and (v2.b < 50 or v2.b = 19)",
"materialized": {
@@ -4096,7 +4096,7 @@ EXPLAIN
"attached_condition": "v2.max_c > 300"
},
"buffer_type": "incremental",
"buffer_size": "9Kb",
"buffer_size": "9KiB",
"join_type": "BNL",
"attached_condition": "v2.a = v1.a or v1.a = t2.a",
"materialized": {
@@ -5847,7 +5847,7 @@ EXPLAIN
"attached_condition": "v_union.a = 1"
},
"buffer_type": "incremental",
"buffer_size": "4Kb",
"buffer_size": "4KiB",
"join_type": "BNL",
"attached_condition": "v_union.c > 800 or v1.max_c > 200",
"materialized": {

View File

@@ -487,7 +487,7 @@ ANALYZE
"r_filtered": 100
},
"buffer_type": "flat",
"buffer_size": "1Kb",
"buffer_size": "1KiB",
"join_type": "BNL",
"attached_condition": "trigcond(t11.col1 = t10.col1)",
"r_loops": 500,

View File

@@ -455,7 +455,7 @@ EXPLAIN
"attached_condition": "tbl2.b < 5"
},
"buffer_type": "flat",
"buffer_size": "1Kb",
"buffer_size": "1KiB",
"join_type": "BNL",
"attached_condition": "tbl2.a = tbl1.a"
}
@@ -768,7 +768,7 @@ EXPLAIN
"filtered": 100
},
"buffer_type": "flat",
"buffer_size": "1Kb",
"buffer_size": "1KiB",
"join_type": "BNL"
}
}
@@ -1048,7 +1048,7 @@ EXPLAIN
"filtered": 100
},
"buffer_type": "flat",
"buffer_size": "1Kb",
"buffer_size": "1KiB",
"join_type": "BNL",
"attached_condition": "tbl2.b = tbl1.b"
}

View File

@@ -386,7 +386,7 @@ EXPLAIN
"filtered": 100
},
"buffer_type": "flat",
"buffer_size": "256Kb",
"buffer_size": "256KiB",
"join_type": "BNL"
}
}
@@ -469,7 +469,7 @@ ANALYZE
"r_filtered": 100
},
"buffer_type": "flat",
"buffer_size": "256Kb",
"buffer_size": "256KiB",
"join_type": "BNL",
"r_loops": 3,
"r_filtered": 100,
@@ -574,7 +574,7 @@ ANALYZE
"r_filtered": 100
},
"buffer_type": "flat",
"buffer_size": "256Kb",
"buffer_size": "256KiB",
"join_type": "BNL",
"r_loops": 3,
"r_filtered": 100,

View File

@@ -97,7 +97,7 @@ JSON
"using_index": true
},
"buffer_type": "flat",
"buffer_size": "256Kb",
"buffer_size": "256KiB",
"join_type": "BNL",
"attached_condition": "trigcond(trigcond(t3.c = 1))",
"r_loops": 10000,

View File

@@ -4020,7 +4020,7 @@ ANALYZE
"r_filtered": 100
},
"buffer_type": "flat",
"buffer_size": "8Kb",
"buffer_size": "8KiB",
"join_type": "BKA",
"mrr_type": "Rowid-ordered scan",
"attached_condition": "a.atp = 1",
@@ -4051,7 +4051,7 @@ ANALYZE
"r_filtered": 100
},
"buffer_type": "incremental",
"buffer_size": "4Kb",
"buffer_size": "4KiB",
"join_type": "BKA",
"mrr_type": "Rowid-ordered scan",
"attached_condition": "fi.fh in (6311439873746261694,-397087483897438286,8518228073041491534,-5420422472375069774)",

View File

@@ -212,7 +212,7 @@ void Json_writer::add_ull(ulonglong val)
}
/* Add a memory size, printing in Kb, Mb if necessary */
/* Add a memory size, printing in KiB, MiB if necessary */
void Json_writer::add_size(longlong val)
{
char buf[64];
@@ -220,10 +220,10 @@ void Json_writer::add_size(longlong val)
if (val < 1024)
len= my_snprintf(buf, sizeof(buf), "%lld", val);
else if (val < 1024*1024*16)
/* Values less than 16MB are specified in KB for precision */
len= my_snprintf(buf, sizeof(buf), "%lldKb", val/1024);
/* Values less than 16MB are specified in KiB for precision */
len= my_snprintf(buf, sizeof(buf), "%lldKiB", val/1024);
else
len= my_snprintf(buf, sizeof(buf), "%lldMb", val/(1024*1024));
len= my_snprintf(buf, sizeof(buf), "%lldMiB", val/(1024*1024));
add_str(buf, len);
}
@@ -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;
}

View File

@@ -523,8 +523,12 @@ public:
}
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)

View File

@@ -21,8 +21,7 @@
#include <tap.h>
/*
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 @@ public:
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(130);
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,905 @@ 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.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("name").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");
ok(!w.invalid_json, "Empty string value is valid");
}
/* 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)");
ok(!w.invalid_json,
"String with embedded quote remains valid for this writer");
}
{
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");
ok(!w.invalid_json,
"Latin-1 encoded string bytes pass through unchanged and stay valid");
}
{
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");
ok(!w.invalid_json,
"UTF-8 mb4 encoded string bytes pass through unchanged and stay valid");
}
{
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");
ok(!w.invalid_json, "Positive longlong value is valid");
}
{
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");
ok(!w.invalid_json, "Negative longlong value is valid");
}
{
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");
ok(!w.invalid_json, "LLONG_MAX is valid");
}
{
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");
ok(!w.invalid_json, "LLONG_MIN is valid");
}
{
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");
ok(!w.invalid_json, "ULLONG_MAX is valid");
}
{
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");
ok(!w.invalid_json, "Bool true is valid");
}
{
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");
ok(!w.invalid_json, "Null value is valid");
}
{
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");
ok(!w.invalid_json, "Double value is valid");
}
{
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");
ok(!w.invalid_json, "Double zero is valid");
}
{
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");
ok(!w.invalid_json, "Double negative zero is valid");
}
{
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");
ok(!w.invalid_json, "Double very small value is valid");
}
{
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");
ok(!w.invalid_json, "Double very large value is valid");
}
{
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");
ok(!w.invalid_json, "Key with spaces is valid");
}
{
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");
ok(!w.invalid_json, "Key with Latin-1 byte is valid");
}
{
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)");
ok(!w.invalid_json,
"Key with embedded quote remains valid for this writer");
}
{
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");
ok(!w.invalid_json, "Empty key name is valid");
}
{
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");
ok(!w.invalid_json, "Named array with single element is valid");
}
{
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)");
ok(!w.invalid_json, "add_size: bytes (< 1024) is valid");
}
{
Json_writer w;
w.start_object();
w.add_member("size").add_size(2048);
w.end_object();
ok(json_output_eq(w,
"{\n"
" \"size\": \"2KiB\"\n"
"}"),
"add_size: KiB value");
ok(!w.invalid_json, "add_size: KiB value is valid");
}
{
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\": \"32MiB\"\n"
"}"),
"add_size: MiB value");
ok(!w.invalid_json, "add_size: MiB value is valid");
}
{
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\": \"1KiB\",\n"
" \"below_16mb\": \"16383KiB\",\n"
" \"at_16mb\": \"16MiB\"\n"
"}"),
"add_size boundary formatting");
ok(!w.invalid_json, "add_size boundary formatting is valid");
}
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");
ok(!w.invalid_json, "add_str with String object is valid");
}
diag("Done");
@@ -146,4 +1071,3 @@ int main(int args, char **argv)
my_end(MY_CHECK_ERROR);
return exit_status();
}