diff options
author | Toni Uhlig <matzeton@googlemail.com> | 2023-07-16 02:03:33 +0200 |
---|---|---|
committer | Toni Uhlig <matzeton@googlemail.com> | 2023-07-16 02:03:33 +0200 |
commit | 5a40295c4cf0af5ea8da9ced04a4ce7d3621a080 (patch) | |
tree | cb21506e7b04d10b45d6066a0ee1655563d5d52b /test/json_test |
Squashed 'flatcc/' content from commit 473da2a
git-subtree-dir: flatcc
git-subtree-split: 473da2afa5ca435363f8c5e6569167aee6bc31c5
Diffstat (limited to 'test/json_test')
-rw-r--r-- | test/json_test/CMakeLists.txt | 64 | ||||
-rw-r--r-- | test/json_test/flatcc_golden.c | 45 | ||||
-rwxr-xr-x | test/json_test/json_test.sh | 74 | ||||
-rw-r--r-- | test/json_test/test_basic_parse.c | 291 | ||||
-rw-r--r-- | test/json_test/test_json.c | 882 | ||||
-rw-r--r-- | test/json_test/test_json_parser.c | 164 | ||||
-rw-r--r-- | test/json_test/test_json_printer.c | 129 |
7 files changed, 1649 insertions, 0 deletions
diff --git a/test/json_test/CMakeLists.txt b/test/json_test/CMakeLists.txt new file mode 100644 index 0000000..332905d --- /dev/null +++ b/test/json_test/CMakeLists.txt @@ -0,0 +1,64 @@ +include(CTest) + +set(INC_DIR "${PROJECT_SOURCE_DIR}/include") +set(GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") +set(FBS_DIR "${PROJECT_SOURCE_DIR}/test/monster_test") + +set(DATA_DST "${CMAKE_CURRENT_BINARY_DIR}") +set(DATA_SRC "${PROJECT_SOURCE_DIR}/test/flatc_compat") + +include_directories("${GEN_DIR}" "${INC_DIR}") + +add_custom_target(gen_monster_test_json ALL) +add_custom_command ( + TARGET gen_monster_test_json + COMMAND ${CMAKE_COMMAND} -E make_directory "${GEN_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy "${DATA_SRC}/monsterdata_test.golden" "${DATA_DST}" + COMMAND ${CMAKE_COMMAND} -E copy "${DATA_SRC}/monsterdata_test.mon" "${DATA_DST}" + COMMAND flatcc_cli -av --json -o "${GEN_DIR}" "${FBS_DIR}/monster_test.fbs" + DEPENDS flatcc_cli "${FBS_DIR}/monster_test.fbs" "${FBS_DIR}/include_test1.fbs" "${FBS_DIR}/include_test2.fbs" +) + +add_executable(test_basic_parse test_basic_parse.c) +add_executable(test_json_parser test_json_parser.c) +add_executable(test_json_printer test_json_printer.c) +add_executable(test_json test_json.c) + +add_dependencies(test_basic_parse gen_monster_test_json) +add_dependencies(test_json_parser gen_monster_test_json) +add_dependencies(test_json_printer gen_monster_test_json) +add_dependencies(test_json gen_monster_test_json) + +target_link_libraries(test_basic_parse flatccrt) +target_link_libraries(test_json_parser flatccrt) +target_link_libraries(test_json_printer flatccrt) +target_link_libraries(test_json flatccrt) + +add_test(test_basic_parse test_basic_parse${CMAKE_EXECUTABLE_SUFFIX}) +add_test(test_json_parser test_json_parser${CMAKE_EXECUTABLE_SUFFIX}) +add_test(test_json_printer test_json_printer${CMAKE_EXECUTABLE_SUFFIX}) +add_test(test_json test_json${CMAKE_EXECUTABLE_SUFFIX}) + +# Compile without default library in order to test various runtime flags +set(RTPATH "${PROJECT_SOURCE_DIR}/src/runtime") +set(RTSRC + "${RTPATH}/builder.c" + "${RTPATH}/emitter.c" + "${RTPATH}/refmap.c" + "${RTPATH}/verifier.c" + "${RTPATH}/json_parser.c" + "${RTPATH}/json_printer.c" +) + +macro(jstest trg flags) + add_executable(${trg} test_json.c ${RTSRC}) + add_dependencies(${trg} gen_monster_test_json) + add_test(${trg} ${trg}${CMAKE_EXECUTABLE_SUFFIX}) + set_target_properties(${trg} PROPERTIES COMPILE_FLAGS ${flags}) +endmacro() + +jstest(json_test_uql "-DFLATCC_JSON_PARSE_ALLOW_UNQUOTED_LIST=1") +jstest(json_test_uql_off "-DFLATCC_JSON_PARSE_ALLOW_UNQUOTED_LIST=0") +jstest(json_test_uq "-DFLATCC_JSON_PARSE_ALLOW_UNQUOTED=1") +jstest(json_test_uq_off "-DFLATCC_JSON_PARSE_ALLOW_UNQUOTED=0") +jstest(json_test "-DFLATCC_JSON_PARSE_WIDE_SPACE=1") diff --git a/test/json_test/flatcc_golden.c b/test/json_test/flatcc_golden.c new file mode 100644 index 0000000..a22e3d3 --- /dev/null +++ b/test/json_test/flatcc_golden.c @@ -0,0 +1,45 @@ +/* + * Flatcc generated monster test binary based on parsing Google flatc's + * golden json file. + */ +static const unsigned char flatcc_golden_le[] = { + 0x0c, 0x00, 0x00, 0x00, 0x4d, 0x4f, 0x4e, 0x53, 0x00, 0x00, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x40, 0x02, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x41, 0xc9, 0x79, 0xdd, + 0x41, 0xc9, 0x79, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x81, 0x91, 0x7b, 0xf2, 0xcd, 0x80, 0x0f, 0x6e, + 0x81, 0x91, 0x7b, 0xf2, 0xcd, 0x80, 0x0f, 0x6e, 0x71, 0xa4, 0x81, 0x8e, 0x71, 0xa4, 0x81, 0x8e, + 0xf1, 0xdd, 0x67, 0xc7, 0xdc, 0x48, 0xf9, 0x43, 0xf1, 0xdd, 0x67, 0xc7, 0xdc, 0x48, 0xf9, 0x43, + 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x74, 0x65, 0x73, 0x74, 0x32, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, + 0x31, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x14, 0x00, 0x1e, 0x00, 0x28, 0x00, + 0xd0, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x46, 0x72, 0x65, 0x64, + 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x4d, 0x79, 0x4d, 0x6f, 0x6e, 0x73, 0x74, 0x65, 0x72, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x34, 0x00, 0x74, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x24, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x34, 0x00, + 0x30, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x44, 0x00, 0x4c, 0x00, 0x54, 0x00, 0x5c, 0x00, 0x60, 0x00, 0x64, 0x00, 0x6c, 0x00, +}; + +static const unsigned char flatcc_golden_be[] = { + 0x00, 0x00, 0x00, 0x0c, 0x53, 0x4e, 0x4f, 0x4d, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x20, + 0x3f, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x05, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x74, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x38, 0xdd, 0x79, 0xc9, 0x41, + 0xdd, 0x79, 0xc9, 0x41, 0x00, 0x00, 0x00, 0x00, 0x6e, 0x0f, 0x80, 0xcd, 0xf2, 0x7b, 0x91, 0x81, + 0x6e, 0x0f, 0x80, 0xcd, 0xf2, 0x7b, 0x91, 0x81, 0x8e, 0x81, 0xa4, 0x71, 0x8e, 0x81, 0xa4, 0x71, + 0x43, 0xf9, 0x48, 0xdc, 0xc7, 0x67, 0xdd, 0xf1, 0x43, 0xf9, 0x48, 0xdc, 0xc7, 0x67, 0xdd, 0xf1, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, + 0x74, 0x65, 0x73, 0x74, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x74, 0x65, 0x73, 0x74, + 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0a, 0x14, 0x00, 0x00, 0x1e, 0x28, 0x00, + 0xff, 0xff, 0xff, 0xd0, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x46, 0x72, 0x65, 0x64, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x09, 0x4d, 0x79, 0x4d, 0x6f, 0x6e, 0x73, 0x74, 0x65, 0x72, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x34, 0x00, 0x74, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x24, 0x00, 0x28, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x34, + 0x00, 0x30, 0x00, 0x38, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x44, 0x00, 0x4c, 0x00, 0x54, 0x00, 0x5c, 0x00, 0x60, 0x00, 0x64, 0x00, 0x6c, +}; diff --git a/test/json_test/json_test.sh b/test/json_test/json_test.sh new file mode 100755 index 0000000..e08bab5 --- /dev/null +++ b/test/json_test/json_test.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +set -e +cd `dirname $0`/../.. +ROOT=`pwd` +TMP=${ROOT}/build/tmp/test/json_test + +CC=${CC:-cc} +${ROOT}/scripts/build.sh +mkdir -p ${TMP} +rm -rf ${TMP}/* + +# could also use --json to generate both at once +bin/flatcc -av --json -o ${TMP} test/monster_test/monster_test.fbs + +cp test/json_test/*.c ${TMP} +cp test/flatc_compat/monsterdata_test.golden ${TMP} +cp test/flatc_compat/monsterdata_test.mon ${TMP} + +cd ${TMP} + +$CC -g -I ${ROOT}/include test_basic_parse.c \ + ${ROOT}/lib/libflatccrt_d.a -o test_basic_parse_d + +$CC -g -I ${ROOT}/include test_json_parser.c \ + ${ROOT}/lib/libflatccrt_d.a -o test_json_parser_d + +$CC -g -I ${ROOT}/include test_json_printer.c \ + ${ROOT}/lib/libflatccrt_d.a -o test_json_printer_d + +$CC -g -I ${ROOT}/include test_json.c\ + ${ROOT}/lib/libflatccrt_d.a -o test_json_d + + +echo "running json basic parse test debug" +./test_basic_parse_d + +echo "running json parser test debug" +./test_json_parser_d + +echo "running json printer test debug" +./test_json_printer_d + +echo "running json test debug" +./test_json_d + +$CC -O2 -DNDEBUG -I ${ROOT}/include test_basic_parse.c \ + ${ROOT}/lib/libflatccrt.a -o test_basic_parse + +#$CC -O3 -DNDEBUG -I ${ROOT}/include test_json_parser.c \ +#$CC -Os -save-temps -DNDEBUG -I ${ROOT}/include test_json_parser.c \ + +$CC -O2 -DNDEBUG -I ${ROOT}/include test_json_parser.c \ + ${ROOT}/lib/libflatccrt.a -o test_json_parser + +$CC -O2 -DNDEBUG -I ${ROOT}/include test_json_printer.c\ + ${ROOT}/lib/libflatccrt.a -o test_json_printer + +$CC -O2 -DNDEBUG -I ${ROOT}/include test_json.c\ + ${ROOT}/lib/libflatccrt.a -o test_json + +echo "running json basic parse test optimized" +./test_basic_parse + +echo "running json parser test optimized" +./test_json_parser + +echo "running json printer test optimimized" +./test_json_printer + +echo "running json test optimized" +./test_json + +echo "json tests passed" diff --git a/test/json_test/test_basic_parse.c b/test/json_test/test_basic_parse.c new file mode 100644 index 0000000..7b8f4ba --- /dev/null +++ b/test/json_test/test_basic_parse.c @@ -0,0 +1,291 @@ +#include <stdio.h> +#include "flatcc/flatcc_builder.h" +#include "flatcc/flatcc_json_parser.h" + +/* + * Helper macros for generating compile time tries. + * + * - this is for prototyping - codegenerator does this without macroes. + */ +#define __FLATCC_CHARW(x, p) (((uint64_t)(x)) << ((p) * 8)) +#define __FLATCC_KW1(s) (__FLATCC_CHARW(s[0], 7)) +#define __FLATCC_KW2(s) (__FLATCC_KW1(s) | __FLATCC_CHARW(s[1], 6)) +#define __FLATCC_KW3(s) (__FLATCC_KW2(s) | __FLATCC_CHARW(s[2], 5)) +#define __FLATCC_KW4(s) (__FLATCC_KW3(s) | __FLATCC_CHARW(s[3], 4)) +#define __FLATCC_KW5(s) (__FLATCC_KW4(s) | __FLATCC_CHARW(s[4], 3)) +#define __FLATCC_KW6(s) (__FLATCC_KW5(s) | __FLATCC_CHARW(s[5], 2)) +#define __FLATCC_KW7(s) (__FLATCC_KW6(s) | __FLATCC_CHARW(s[6], 1)) +#define __FLATCC_KW8(s) (__FLATCC_KW7(s) | __FLATCC_CHARW(s[7], 0)) +#define __FLATCC_KW(s, n) __FLATCC_KW ## n(s) + +#define __FLATCC_MASKKW(n) ((~(uint64_t)0) << ((8 - (n)) * 8)) +#define __FLATCC_MATCHKW(w, s, n) ((__FLATCC_MASKKW(n) & (w)) == __FLATCC_KW(s, n)) +#define __FLATCC_LTKW(w, s, n) ((__FLATCC_MASKKW(n) & (w)) < __FLATCC_KW(s, n)) + + +const char g_data[] = " \ + \ +{ \r\n \ + \"first\": 1, \ + \"second\": 2.0, \ + \"seconds left\": 42, \ + \"seconds lead\": 1, \n \ + \"zulu\": \"really\" \n \ +} \ +"; + +/* + * This is proof of concept test before code-generation to evaluate + * efficient parsing and buffer construction principles while scanning + * text such as a JSON. We do no use a schema per se, but implicitly + * define one in the way that we construct the parser. + */ + +#define match(x) if (end > buf && buf[0] == x) { ++buf; } \ + else { fprintf(stderr, "failed to match '%c'\n", x); \ + buf = flatcc_json_parser_set_error(ctx, buf, end, \ + flatcc_json_parser_error_invalid_character); goto fail; } + +/* Space is optional, but we do expect more input. */ +#define space() { \ + buf = flatcc_json_parser_space(ctx, buf, end); \ + if (buf == end) { fprintf(stderr, "parse failed\n"); goto fail; }} \ + +#ifdef FLATCC_JSON_ALLOW_UNKNOWN_FIELD +#define ignore_field() { \ + buf = flatcc_json_parser_symbol_end(ctx, buf, end); \ + space(); match(':'); space(); \ + buf = flatcc_json_parser_generic_json(ctx, buf, end); \ + if (buf == end) { \ + goto fail; \ + }} +#else +#define ignore_field() { \ + buf = flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unknown_symbol);\ + goto fail; } +#endif + + +/* + * We build a flatbuffer dynamically without a schema, but we still need + * to assigned vtable entries. + */ +enum { + id_first = 0, + id_second = 1, + id_seconds_left = 2, + id_seconds_lead = 3, + id_zulu = 10 +}; + +enum { + ctx_done = 0, ctx_t1_start, ctx_t1_again +}; + +const char *test(flatcc_builder_t *B, const char *buf, const char *end, int *ret) +{ + flatcc_json_parser_t parse_ctx, *ctx; + flatcc_builder_ref_t root = 0, ref, *p_ref; + uint64_t w; + const char *k; + char *s; + flatcc_json_parser_escape_buffer_t code; + + void *p; + + ctx = &parse_ctx; + memset(ctx, 0, sizeof(*ctx)); + ctx->line = 1; + ctx->line_start = buf; + + flatcc_builder_start_buffer(B, "TEST", 0, 0); + + space(); match('{'); space(); + flatcc_builder_start_table(B, id_zulu + 1); + +t1_again: + + buf = flatcc_json_parser_symbol_start(ctx, buf, end); + w = flatcc_json_parser_symbol_part(buf, end); + k = end - buf > 8 ? buf + 8 : end; + /* + * We implement a trie here. Because we compare big endian + * any trailing garbage in a word is least significant + * and masked out in MATCH tests. + * + * When a keyword is a prefix of another, the shorter keyword + * must be tested first because any trailing "garbage" will + * be larger (or equal if at buffer end or invalid nulls are + * contained) than the short keyword, but if testing the long + * keyword, the shorter keyword may be either larger or smaller + * depending on what content follows. + * + * Errors result in `buf` being set to `end` so we need not test + * for errors all the time. We use space as a convenient bailout + * point. + */ + if (__FLATCC_LTKW(w, "second", 6)) { + if (!__FLATCC_MATCHKW(w, "first", 5)) { + ignore_field(); + } else { + buf = flatcc_json_parser_symbol_end(ctx, buf + 5, end); + space(); match(':'); space(); + p = flatcc_builder_table_add(B, id_first, 1, 1); + if (!p) { goto fail; } + k = buf; + buf = flatcc_json_parser_uint8(ctx, buf, end, p); + /* Here we could optionally parse for symbolic constants. */ + if (k == buf) { goto fail; }; + /* Successfully parsed field. */ + } + } else { + if (__FLATCC_LTKW(w, "zulu", 4)) { + if (__FLATCC_LTKW(w, "seconds ", 8)) { + if (!__FLATCC_MATCHKW(w, "second", 6)) { + ignore_field(); + } else { + buf = flatcc_json_parser_symbol_end(ctx, buf + 6, end); + space(); match(':'); space(); + p = flatcc_builder_table_add(B, id_second, 8, 8); + if (!p) { goto fail; } + k = buf; + buf = flatcc_json_parser_double(ctx, buf, end, p); + /* Here we could optionally parse for symbolic constants. */ + if (k == buf) { goto fail; }; + /* Successfully parsed field. */ + } + } else { + if (!__FLATCC_MATCHKW(w, "seconds ", 8)) { + ignore_field(); + } else { + /* We have multiple keys matching the first word, so we load another. */ + buf = k; + w = flatcc_json_parser_symbol_part(buf, end); + k = end - buf > 8 ? buf + 8 : end; + if (__FLATCC_LTKW(w, "left", 4)) { + if (!__FLATCC_MATCHKW(w, "lead", 4)) { + ignore_field(); + } else { + buf = flatcc_json_parser_symbol_end(ctx, buf + 4, end); + space(); match(':'); space(); + p = flatcc_builder_table_add(B, id_seconds_lead, 8, 8); + if (!p) { goto fail; } + k = buf; + buf = flatcc_json_parser_int64(ctx, buf, end, p); + /* Here we could optionally parse for symbolic constants. */ + if (k == buf) { goto fail; }; + /* Successfully parsed field. */ + } + } else { + if (!__FLATCC_MATCHKW(w, "left", 4)) { + ignore_field(); + } else { + buf = flatcc_json_parser_symbol_end(ctx, buf + 4, end); + space(); match(':'); space(); + p = flatcc_builder_table_add(B, id_seconds_left, 4, 4); + if (!p) { goto fail; } + k = buf; + buf = flatcc_json_parser_uint32(ctx, buf, end, p); + /* Here we could optionally parse for symbolic constants. */ + if (k == buf) { goto fail; }; + /* Successfully parsed field. */ + } + } + } + } + } else { + if (!__FLATCC_MATCHKW(w, "zulu", 4)) { + ignore_field(); + } else { + buf = flatcc_json_parser_symbol_end(ctx, buf + 4, end); + space(); match(':'); space(); + /* + * Parse field as string. If we are lucky, we can + * create the string in one go, which is faster. + * We can't if the string contains escape codes. + */ + buf = flatcc_json_parser_string_start(ctx, buf, end); + k = buf; + buf = flatcc_json_parser_string_part(ctx, buf, end); + if (buf == end) { + goto fail; + } + if (buf[0] == '\"') { + ref = flatcc_builder_create_string(B, k, (size_t)(buf - k)); + } else { + /* Start string with enough space for what we have. */ + flatcc_builder_start_string(B); + s = flatcc_builder_extend_string(B, (size_t)(buf - k)); + if (!s) { goto fail; } + memcpy(s, k, (size_t)(buf - k)); + do { + buf = flatcc_json_parser_string_escape(ctx, buf, end, code); + flatcc_builder_append_string(B, code + 1, (size_t)code[0]); + k = buf; + buf = flatcc_json_parser_string_part(ctx, buf, end); + if (buf == end) { + goto fail; + } + flatcc_builder_append_string(B, k, (size_t)(buf - k)); + } while (buf[0] != '\"'); + ref = flatcc_builder_end_string(B); + } + if (!ref) { + goto fail; + } + /* Duplicate fields may fail or assert. */ + p_ref = flatcc_builder_table_add_offset(B, id_zulu); + if (!p_ref) { + goto fail; + } + *p_ref = ref; + buf = flatcc_json_parser_string_end(ctx, buf, end); + /* Successfully parsed field. */ + } + } + } + space(); + if (*buf == ',') { + ++buf; + space(); + if (*buf != '}') { + goto t1_again; + } +#if !FLATCC_JSON_PARSE_ALLOW_TRAILING_COMMA + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_trailing_comma); +#endif + } + match('}'); + root = flatcc_builder_end_table(B); + + flatcc_builder_end_buffer(B, root); +#if !FLATCC_JSON_PARSE_IGNORE_TRAILING_DATA + buf = flatcc_json_parser_space(ctx, buf, end); + if (buf != end) { + fprintf(stderr, "extra characters in input\n"); + goto fail; + } +#endif +fail: + if (ctx->error) { + fprintf(stderr, "%d:%d: %s\n", (int)ctx->line, (int)(ctx->error_loc - ctx->line_start + 1), flatcc_json_parser_error_string(ctx->error)); + flatcc_builder_reset(B); + } else { + fprintf(stderr, "parse accepted\n"); + } + *ret = ctx->error; + return buf; +} + +int main(void) +{ + int ret = -1; + flatcc_builder_t builder; + + flatcc_builder_init(&builder); + + test(&builder, g_data, g_data + sizeof(g_data) - 1, &ret); + + flatcc_builder_clear(&builder); + return ret; +} diff --git a/test/json_test/test_json.c b/test/json_test/test_json.c new file mode 100644 index 0000000..aeee13a --- /dev/null +++ b/test/json_test/test_json.c @@ -0,0 +1,882 @@ +#include <stdio.h> +#include "monster_test_json_parser.h" +#include "monster_test_json_printer.h" +#include "monster_test_verifier.h" + +#include "flatcc/support/hexdump.h" + +#define UQL FLATCC_JSON_PARSE_ALLOW_UNQUOTED_LIST +#define UQ FLATCC_JSON_PARSE_ALLOW_UNQUOTED + +#undef ns +#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Example, x) + +#undef nsf +#define nsf(x) FLATBUFFERS_WRAP_NAMESPACE(Fantasy, x) + +struct test_scope { + const char *identifier; + flatcc_json_parser_table_f *parser; + flatcc_json_printer_table_f *printer; + flatcc_table_verifier_f *verifier; +}; + +static const struct test_scope Monster = { + /* The is the schema global file identifier. */ + ns(Monster_file_identifier), + ns(Monster_parse_json_table), + ns(Monster_print_json_table), + ns(Monster_verify_table) +}; + +static const struct test_scope Alt = { + /* This is the type hash identifier. */ + ns(Alt_type_identifier), + ns(Alt_parse_json_table), + ns(Alt_print_json_table), + ns(Alt_verify_table) +}; + +static const struct test_scope Movie = { + /* This is the type hash identifier. */ + nsf(Movie_type_identifier), + nsf(Movie_parse_json_table), + nsf(Movie_print_json_table), + nsf(Movie_verify_table) +}; + +int test_json(const struct test_scope *scope, char *json, + char *expect, int expect_err, + flatcc_json_parser_flags_t parse_flags, flatcc_json_printer_flags_t print_flags, int line) +{ + int ret = -1; + int err; + void *flatbuffer = 0; + char *buf = 0; + size_t flatbuffer_size, buf_size; + flatcc_builder_t builder, *B; + flatcc_json_parser_t parser_ctx; + flatcc_json_printer_t printer_ctx; + int i; + + B = &builder; + flatcc_builder_init(B); + flatcc_json_printer_init_dynamic_buffer(&printer_ctx, 0); + flatcc_json_printer_set_flags(&printer_ctx, print_flags); + err = flatcc_json_parser_table_as_root(B, &parser_ctx, json, strlen(json), parse_flags, + scope->identifier, scope->parser); + if (err != expect_err) { + if (expect_err) { + if (err) { + fprintf(stderr, "%d: json test: parse failed with: %s\n", + line, flatcc_json_parser_error_string(err)); + fprintf(stderr, "but expected to fail with: %s\n", + flatcc_json_parser_error_string(expect_err)); + fprintf(stderr, "%s\n", json); + } else { + fprintf(stderr, "%d: json test: parse successful, but expected to fail with: %s\n", + line, flatcc_json_parser_error_string(expect_err)); + fprintf(stderr, "%s\n", json); + } + } else { + fprintf(stderr, "%d: json test: parse failed: %s\n", line, flatcc_json_parser_error_string(err)); + fprintf(stderr, "%s\n", json); + } + for (i = 0; i < parser_ctx.pos - 1; ++i) { + fprintf(stderr, " "); + } + fprintf(stderr, "^\n"); + goto failed; + } + if (expect_err) { + ret = 0; + goto done; + } + flatbuffer = flatcc_builder_finalize_aligned_buffer(B, &flatbuffer_size); + if ((ret = flatcc_verify_table_as_root(flatbuffer, flatbuffer_size, scope->identifier, scope->verifier))) { + fprintf(stderr, "%s:%d: buffer verification failed: %s\n", + __FILE__, line, flatcc_verify_error_string(ret)); + goto failed; + } + + flatcc_json_printer_table_as_root(&printer_ctx, flatbuffer, flatbuffer_size, scope->identifier, scope->printer); + buf = flatcc_json_printer_get_buffer(&printer_ctx, &buf_size); + if (!buf || strcmp(expect, buf)) { + fprintf(stderr, "%d: json test: printed buffer not as expected, got:\n", line); + fprintf(stderr, "%s\n", buf); + fprintf(stderr, "expected:\n"); + fprintf(stderr, "%s\n", expect); + goto failed; + } + ret = 0; + +done: + flatcc_builder_aligned_free(flatbuffer); + flatcc_builder_clear(B); + flatcc_json_printer_clear(&printer_ctx); + return ret; + +failed: + if (flatbuffer) { + hexdump("parsed buffer", flatbuffer, flatbuffer_size, stderr); + } + ret = -1; + goto done; +} + +#define BEGIN_TEST(name) int ret = 0; const struct test_scope *scope = &name +#define END_TEST() return ret; + +#define TEST(x, y) \ + ret |= test_json(scope, (x), (y), 0, 0, 0, __LINE__); + +#define TEST_ERROR(x, err) \ + ret |= test_json(scope, (x), 0, err, 0, 0, __LINE__); + +#define TEST_FLAGS(fparse, fprint, x, y) \ + ret |= test_json(scope, (x), (y), 0, (fparse), (fprint), __LINE__); + +#define TEST_ERROR_FLAGS(fparse, fprint, x, err) \ + ret |= test_json(scope, (x), 0, err, (fparse), (fprint), __LINE__); + +int edge_case_tests(void) +{ + BEGIN_TEST(Monster); +/* + * Each symbolic value is type coerced and added. One might expect + * or'ing flags together, but it doesn't work with signed values + * and floating point target values. We would either need a much + * more complicated parser or restrict the places where symbols are + * allowed. + */ +#if 0 + TEST( "{ name: \"Monster\", color: \"Green Blue Red Blue\"}", + "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}"); +#else +#if UQ + TEST( "{ name: \"Monster\", color: \"Green Blue Red Blue\"}", + "{\"name\":\"Monster\",\"color\":19}"); +#else + TEST( "{ \"name\": \"Monster\", \"color\": \"Green Blue Red Blue\"}", + "{\"name\":\"Monster\",\"color\":19}"); +#endif +#endif + +/* + * If a value is stored, even if default, it is also printed. + * This option can also be flagged compile time for better performance. + */ + TEST_FLAGS(flatcc_json_parser_f_force_add, 0, + "{ \"name\": \"Monster\", \"color\": 8}", + "{\"name\":\"Monster\",\"color\":\"Blue\"}"); + + TEST_FLAGS(0, flatcc_json_printer_f_noenum, + "{ \"name\": \"Monster\", \"color\": \"Green\"}", + "{\"name\":\"Monster\",\"color\":2}"); + + TEST_FLAGS(flatcc_json_parser_f_force_add, flatcc_json_printer_f_skip_default, + "{ \"name\": \"Monster\", \"color\": 8}", + "{\"name\":\"Monster\"}"); + + TEST_FLAGS(0, flatcc_json_printer_f_force_default, + "{ \"name\": \"Monster\", \"testf\":3.0}", +"{\"mana\":150,\"hp\":100,\"name\":\"Monster\",\"color\":\"Blue\",\"testbool\":true,\"testhashs32_fnv1\":0,\"testhashu32_fnv1\":0,\"testhashs64_fnv1\":0,\"testhashu64_fnv1\":0,\"testhashs32_fnv1a\":0,\"testhashu32_fnv1a\":0,\"testhashs64_fnv1a\":0,\"testhashu64_fnv1a\":0,\"testf\":3,\"testf2\":3,\"testf3\":0}"); + + + /* + * Cannot test the default of testf field because float is printed as double with + * configuration dependent precision. + */ +#if 0 + TEST_FLAGS(0, flatcc_json_printer_f_force_default, + "{ \"name\": \"Monster\", \"testf3\":3.14159012}", +"{\"mana\":150,\"hp\":100,\"name\":\"Monster\",\"color\":\"Blue\",\"testbool\":true,\"testhashs32_fnv1\":0,\"testhashu32_fnv1\":0,\"testhashs64_fnv1\":0,\"testhashu64_fnv1\":0,\"testhashs32_fnv1a\":0,\"testhashu32_fnv1a\":0,\"testhashs64_fnv1a\":0,\"testhashu64_fnv1a\":0,\"testf\":3.14159,\"testf2\":3,\"testf3\":0}"); +#endif + + TEST_FLAGS(flatcc_json_parser_f_force_add, 0, + "{ \"name\": \"Monster\", \"color\": \"Blue\"}", + "{\"name\":\"Monster\",\"color\":\"Blue\"}"); + + TEST_FLAGS(flatcc_json_parser_f_skip_unknown, 0, + "{ \"name\": \"Monster\", \"xcolor\": \"Green\", \"hp\": 42}", + "{\"hp\":42,\"name\":\"Monster\"}"); + + TEST_FLAGS(flatcc_json_parser_f_skip_unknown, flatcc_json_printer_f_unquote, + "{ \"name\": \"Monster\", \"xcolor\": \"Green\", \"hp\": 42}", + "{hp:42,name:\"Monster\"}"); + + /* Also test generic parser used with unions with late type field. */ + TEST_FLAGS(flatcc_json_parser_f_skip_unknown, 0, + "{ \"name\": \"Monster\", \"xcolor\": \"Green\", " + "\"foobar\": { \"a\": [1, 2.0, ], \"a1\": {}, \"b\": null, \"c\":[], }, \"hp\": 42 }", + "{\"hp\":42,\"name\":\"Monster\"}"); +#if UQ +/* + * If a value is stored, even if default, it is also printed. + * This option can also be flagged compile time for better performance. + */ + TEST_FLAGS(flatcc_json_parser_f_force_add, 0, + "{ name: \"Monster\", color: 8}", + "{\"name\":\"Monster\",\"color\":\"Blue\"}"); + + TEST_FLAGS(0, flatcc_json_printer_f_noenum, + "{ name: \"Monster\", color: Green}", + "{\"name\":\"Monster\",\"color\":2}"); + + TEST_FLAGS(flatcc_json_parser_f_force_add, flatcc_json_printer_f_skip_default, + "{ name: \"Monster\", color: 8}", + "{\"name\":\"Monster\"}"); + + TEST_FLAGS(0, flatcc_json_printer_f_force_default, + "{ name: \"Monster\"}", +"{\"mana\":150,\"hp\":100,\"name\":\"Monster\",\"color\":\"Blue\",\"testbool\":true,\"testhashs32_fnv1\":0,\"testhashu32_fnv1\":0,\"testhashs64_fnv1\":0,\"testhashu64_fnv1\":0,\"testhashs32_fnv1a\":0,\"testhashu32_fnv1a\":0,\"testhashs64_fnv1a\":0,\"testhashu64_fnv1a\":0,\"testf\":314159,\"testf2\":3,\"testf3\":0}"); + + TEST_FLAGS(flatcc_json_parser_f_force_add, 0, + "{ name: \"Monster\", color: Blue}", + "{\"name\":\"Monster\",\"color\":\"Blue\"}"); + + TEST_FLAGS(flatcc_json_parser_f_skip_unknown, 0, + "{ name: \"Monster\", xcolor: Green, hp: 42}", + "{\"hp\":42,\"name\":\"Monster\"}"); + + TEST_FLAGS(flatcc_json_parser_f_skip_unknown, flatcc_json_printer_f_unquote, + "{ name: \"Monster\", xcolor: Green, hp: 42}", + "{hp:42,name:\"Monster\"}"); + + /* Also test generic parser used with unions with late type field. */ + TEST_FLAGS(flatcc_json_parser_f_skip_unknown, 0, + "{ name: \"Monster\", xcolor: Green, " + "foobar: { a: [1, 2.0, ], a1: {}, b: null, c:[], }, hp: 42 }", + "{\"hp\":42,\"name\":\"Monster\"}"); +#endif + +/* Without skip unknown, we should expect failure. */ +#if 0 + TEST( "{ name: \"Monster\", xcolor: Green}", + "{\"name\":\"Monster\"}"); +#endif + +/* We do not support null. */ +#if 0 + TEST( + "{ name: \"Monster\", test_type: null }", + "{\"name\":\"Monster\"}"); +#endif + +/* + * We do not allow empty flag strings because they might mean + * either default value, or 0. + */ +#if 0 + /* Questionable if this really is an error. */ + TEST( "{ name: \"Monster\", color: \"\"}", + "{\"name\":\"Monster\",\"color\":0}"); // TODO: should this be color:"" ? + + TEST( "{ name: \"Monster\", color: \" \"}", + "{\"name\":\"Monster\",\"color\":0}"); + +#endif + + END_TEST(); +} + +int error_case_tests(void) +{ + BEGIN_TEST(Monster); + + TEST_ERROR( "{ \"nickname\": \"Monster\" }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ \"name\": \"Monster\", \"test_type\": \"Monster\", \"test\": { \"nickname\": \"Joker\", \"color\": \"Red\" } } }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ \"name\": \"Monster\", \"test_type\": \"Monster\", \"test\": { \"name\": \"Joker\", \"colour\": \"Red\" } } }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": [ { \"nickname\": \"Joker\", \"color\": \"Red\" } ] }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": [ { \"name\": \"Joker\", \"colour\": \"Red\" } ] }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": [" + "{ \"name\": \"Joker\", \"color\": \"Red\", \"test_type\": \"Monster\", \"test\": { \"nickname\": \"Harley\", \"color\": \"Blue\" } } ] }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": [" + "{ \"name\": \"Joker\", \"color\": \"Red\", \"test_type\": \"Monster\", \"test\": { \"name\": \"Harley\", \"colour\": \"Blue\" } } ] }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": [" + "{ \"name\": \"Joker\", \"test_type\": \"Monster\", \"test\": { \"nickname\": \"Harley\" } }," + "{ \"name\": \"Bonnie\", \"test_type\": \"Monster\", \"test\": { \"name\": \"Clyde\" } } ] }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ \"name\": \"Monster\", \"testarrayoftables\": [" + "{ \"name\": \"Joker\", \"test_type\": \"Monster\", \"test\": { \"name\": \"Harley\" } }," + "{ \"name\": \"Bonnie\", \"test_type\": \"Monster\", \"test\": { \"nickname\": \"Clyde\" } } ] }", + flatcc_json_parser_error_unknown_symbol ); + +#if !UQ + TEST_ERROR( "{ nickname: \"Monster\" }", + flatcc_json_parser_error_unexpected_character ); + + TEST_ERROR( "{ \"name\": \"Monster\", \"color\": Green }", + flatcc_json_parser_error_unexpected_character ); + + TEST_ERROR( "{ \"name\": \"Monster\", \"color\": Green Red Blue }", + flatcc_json_parser_error_unexpected_character ); +#endif + +#if UQ + TEST_ERROR( "{ nickname: \"Monster\" }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ name: \"Monster\", test_type: Monster, test: { nickname: \"Joker\", color: \"Red\" } } }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ name: \"Monster\", test_type: Monster, test: { name: \"Joker\", colour: \"Red\" } } }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ name: \"Monster\", testarrayoftables: [ { nickname: \"Joker\", color: \"Red\" } ] }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ name: \"Monster\", testarrayoftables: [ { name: \"Joker\", colour: \"Red\" } ] }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ name: \"Monster\", testarrayoftables: [" + "{ name: \"Joker\", color: \"Red\", test_type: Monster, test: { nickname: \"Harley\", color: \"Blue\" } } ] }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ name: \"Monster\", testarrayoftables: [" + "{ name: \"Joker\", color: \"Red\", test_type: Monster, test: { name: \"Harley\", colour: \"Blue\" } } ] }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ name: \"Monster\", testarrayoftables: [" + "{ name: \"Joker\", test_type: Monster, test: { nickname: \"Harley\" } }," + "{ name: \"Bonnie\", test_type: Monster, test: { name: \"Clyde\" } } ] }", + flatcc_json_parser_error_unknown_symbol ); + TEST_ERROR( "{ name: \"Monster\", testarrayoftables: [" + "{ name: \"Joker\", test_type: Monster, test: { name: \"Harley\" } }," + "{ name: \"Bonnie\", test_type: Monster, test: { nickname: \"Clyde\" } } ] }", + flatcc_json_parser_error_unknown_symbol ); + +#endif + + END_TEST(); +} + +#define RANDOM_BASE64 "zLOuiUjH49tz4Ap2JnmpTX5NqoiMzlD8hSw45QCS2yaSp7UYoA" \ + "oE8KpY/5pKYmk+54NI40hyeyZ1zRUE4vKQT0hEdVl0iXq2fqPamkVD1AZlVvQJ1m00PaoXOSgG+64Zv+Uygw==" + +#define RANDOM_BASE64_NOPAD "zLOuiUjH49tz4Ap2JnmpTX5NqoiMzlD8hSw45QCS2yaSp7UYoA" \ + "oE8KpY/5pKYmk+54NI40hyeyZ1zRUE4vKQT0hEdVl0iXq2fqPamkVD1AZlVvQJ1m00PaoXOSgG+64Zv+Uygw" + +#define RANDOM_BASE64URL "zLOuiUjH49tz4Ap2JnmpTX5NqoiMzlD8hSw45QCS2yaSp7UYoA" \ + "oE8KpY_5pKYmk-54NI40hyeyZ1zRUE4vKQT0hEdVl0iXq2fqPamkVD1AZlVvQJ1m00PaoXOSgG-64Zv-Uygw==" + +#define RANDOM_BASE64URL_NOPAD "zLOuiUjH49tz4Ap2JnmpTX5NqoiMzlD8hSw45QCS2yaSp7UYoA" \ + "oE8KpY_5pKYmk-54NI40hyeyZ1zRUE4vKQT0hEdVl0iXq2fqPamkVD1AZlVvQJ1m00PaoXOSgG-64Zv-Uygw" + +int base64_tests(void) +{ + BEGIN_TEST(Monster); + + /* Reference */ + TEST( "{ \"name\": \"Monster\" }", + "{\"name\":\"Monster\"}"); + + TEST( "{ \"name\": \"Monster\", \"testbase64\":{} }", + "{\"name\":\"Monster\",\"testbase64\":{}}"); + + + TEST( "{ \"name\": \"Monster\", \"testbase64\":{ \"data\":\"" RANDOM_BASE64 "\"} }", + "{\"name\":\"Monster\",\"testbase64\":{\"data\":\"" RANDOM_BASE64 "\"}}"); + + TEST( "{ \"name\": \"Monster\", \"testbase64\":{ \"urldata\":\"" RANDOM_BASE64URL "\"} }", + "{\"name\":\"Monster\",\"testbase64\":{\"urldata\":\"" RANDOM_BASE64URL "\"}}"); + + TEST( "{ \"name\": \"Monster\", \"testbase64\":{ \"data\":\"" RANDOM_BASE64_NOPAD "\"} }", + "{\"name\":\"Monster\",\"testbase64\":{\"data\":\"" RANDOM_BASE64 "\"}}"); + + TEST( "{ \"name\": \"Monster\", \"testbase64\":{ \"urldata\":\"" RANDOM_BASE64URL_NOPAD "\"} }", + "{\"name\":\"Monster\",\"testbase64\":{\"urldata\":\"" RANDOM_BASE64URL "\"}}"); + + TEST_ERROR( "{ \"name\": \"Monster\", \"testbase64\":{ \"data\":\"" RANDOM_BASE64URL "\"} }", + flatcc_json_parser_error_base64); + + TEST_ERROR( "{ \"name\": \"Monster\", \"testbase64\":{ \"urldata\":\"" RANDOM_BASE64 "\"} }", + flatcc_json_parser_error_base64url); + +/* Test case from Googles flatc implementation. */ +#if UQ + TEST( "{name: \"Monster\"," + "testbase64: {" + "data: \"23A/47d450+sdfx9+wRYIS09ckas/asdFBQ=\"," + "urldata: \"23A_47d450-sdfx9-wRYIS09ckas_asdFBQ=\"," + "nested: \"FAAAAE1PTlMMAAwAAAAEAAYACAAMAAAAAAAAAAQAAAANAAAATmVzdGVkTW9uc3RlcgAAAA==\"" + "}}", + "{\"name\":\"Monster\"," + "\"testbase64\":{" + "\"data\":\"23A/47d450+sdfx9+wRYIS09ckas/asdFBQ=\"," + "\"urldata\":\"23A_47d450-sdfx9-wRYIS09ckas_asdFBQ=\"," + "\"nested\":\"FAAAAE1PTlMMAAwAAAAEAAYACAAMAAAAAAAAAAQAAAANAAAATmVzdGVkTW9uc3RlcgAAAA==\"" + "}}"); + + TEST( "{name: \"Monster\"," + "testbase64: {" + "data: \"23A/47d450+sdfx9+wRYIS09ckas/asdFBQ\"," + "urldata: \"23A_47d450-sdfx9-wRYIS09ckas_asdFBQ\"," + "nested: \"FAAAAE1PTlMMAAwAAAAEAAYACAAMAAAAAAAAAAQAAAANAAAATmVzdGVkTW9uc3RlcgAAAA\"" + "}}", + "{\"name\":\"Monster\"," + "\"testbase64\":{" + "\"data\":\"23A/47d450+sdfx9+wRYIS09ckas/asdFBQ=\"," + "\"urldata\":\"23A_47d450-sdfx9-wRYIS09ckas_asdFBQ=\"," + "\"nested\":\"FAAAAE1PTlMMAAwAAAAEAAYACAAMAAAAAAAAAAQAAAANAAAATmVzdGVkTW9uc3RlcgAAAA==\"" + "}}"); +#endif + + END_TEST(); +} + +int mixed_type_union_tests(void) +{ + BEGIN_TEST(Movie); + + /* Reference */ + + TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 } }", + "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19}}"); + + TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 }," + " \"side_kick_type\": \"Other\", \"side_kick\": \"a donkey\"}", + "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19}," + "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\"}"); + + TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 }," + " \"side_kick_type\": \"Fantasy.Character.Other\", \"side_kick\": \"a donkey\"}}", + "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19}," + "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\"}"); + + TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 }," + " \"side_kick_type\": \"Fantasy.Character.Other\", \"side_kick\": \"a donkey\"," + " \"antagonist_type\": \"MuLan\", \"antagonist\": {\"sword_attack_damage\": 42}}", + "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19}," + "\"antagonist_type\":\"MuLan\",\"antagonist\":{\"sword_attack_damage\":42}," + "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\"}"); + + TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 }," + " \"side_kick_type\": \"Fantasy.Character.Other\", \"side_kick\": \"a donkey\"," + " \"antagonist_type\": \"MuLan\", \"antagonist\": {\"sword_attack_damage\": 42}," + " \"characters_type\": [], \"characters\": []}", + "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19}," + "\"antagonist_type\":\"MuLan\",\"antagonist\":{\"sword_attack_damage\":42}," + "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\"," + "\"characters_type\":[],\"characters\":[]}") + + TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 }," + " \"side_kick_type\": \"Fantasy.Character.Other\", \"side_kick\": \"a donkey\"," + " \"antagonist_type\": \"MuLan\", \"antagonist\": {\"sword_attack_damage\": 42}," + " \"characters_type\": [\"Fantasy.Character.Rapunzel\", \"Other\", 0, \"MuLan\"]," + " \"characters\": [{\"hair_length\":19}, \"unattributed extras\", null, {\"sword_attack_damage\":2}]}", + "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19}," + "\"antagonist_type\":\"MuLan\",\"antagonist\":{\"sword_attack_damage\":42}," + "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\"," + "\"characters_type\":[\"Rapunzel\",\"Other\",\"NONE\",\"MuLan\"]," + "\"characters\":[{\"hair_length\":19},\"unattributed extras\",null,{\"sword_attack_damage\":2}]}") + + TEST( "{ \"main_character_type\": \"Rapunzel\", \"main_character\": { \"hair_length\": 19 }," + " \"side_kick_type\": \"Character.Other\", \"side_kick\": \"a donkey\"}", + "{\"main_character_type\":\"Rapunzel\",\"main_character\":{\"hair_length\":19}," + "\"side_kick_type\":\"Other\",\"side_kick\":\"a donkey\"}"); + + END_TEST(); +} + +int union_vector_tests(void) +{ + BEGIN_TEST(Alt); + /* Union vector */ + + TEST( "{ \"manyany_type\": [ \"Monster\" ], \"manyany\": [{\"name\": \"Joe\"}] }", + "{\"manyany_type\":[\"Monster\"],\"manyany\":[{\"name\":\"Joe\"}]}"); + + TEST( "{\"manyany_type\": [ \"NONE\" ], \"manyany\": [ null ] }", + "{\"manyany_type\":[\"NONE\"],\"manyany\":[null]}"); + + TEST( "{\"manyany_type\": [ \"Monster\", \"NONE\" ], \"manyany\": [{\"name\": \"Joe\"}, null] }", + "{\"manyany_type\":[\"Monster\",\"NONE\"],\"manyany\":[{\"name\":\"Joe\"},null]}"); + + TEST( "{\"manyany_type\": [ \"Monster\" ], \"manyany\": [ { \"name\":\"Joe\", \"test_type\": \"Monster\", \"test\": { \"name\": \"any Monster\" } } ] }", + "{\"manyany_type\":[\"Monster\"],\"manyany\":[{\"name\":\"Joe\",\"test_type\":\"Monster\",\"test\":{\"name\":\"any Monster\"}}]}"); + + TEST( "{\"manyany\": [{\"name\": \"Joe\"}], \"manyany_type\": [ \"Monster\" ] }", + "{\"manyany_type\":[\"Monster\"],\"manyany\":[{\"name\":\"Joe\"}]}"); + + TEST( "{\"manyany\": [{\"manyany\":[null, null], \"manyany_type\": [\"NONE\", \"NONE\"]}], \"manyany_type\": [ \"Alt\" ] }", + "{\"manyany_type\":[\"Alt\"],\"manyany\":[{\"manyany_type\":[\"NONE\",\"NONE\"],\"manyany\":[null,null]}]}"); + + END_TEST(); +} + +int fixed_array_tests(void) +{ + BEGIN_TEST(Alt); + /* Fixed array */ + +#if UQ + TEST( "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0, 0, 0, 0, 0, 0," + " 0, 0, 0, 0, 0, 0, 0, 0, 16.0], col:[\"Blue Red\", Green, Red]," + "tests:[ {b:4}, {a:1, b:2}]," + " \"bar\": [ 100, 0, 0, 0, 0, 0, 0, 0, 0, 1000]," + " \"text\":\"hello\"}}", + "{\"fixed_array\":{\"foo\":[1,2,0,0,0,0,0," + "0,0,0,0,0,0,0,0,16]," + "\"bar\":[100,0,0,0,0,0,0,0,0,1000]," + "\"col\":[\"Red Blue\",\"Green\",\"Red\"]," + "\"tests\":[{\"a\":0,\"b\":4},{\"a\":1,\"b\":2}]," + "\"text\":\"hello\"}}"); +#else + TEST( "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0, 0, 0, 0, 0, 0," + " 0, 0, 0, 0, 0, 0, 0, 0, 16.0], \"col\":[\"Blue Red\", \"Green\", \"Red\"]," + "\"tests\":[ {\"b\":4}, {\"a\":1, \"b\":2}]," + " \"bar\": [ 100, 0, 0, 0, 0, 0, 0, 0, 0, 1000]," + " \"text\":\"hello\"}}", + "{\"fixed_array\":{\"foo\":[1,2,0,0,0,0,0," + "0,0,0,0,0,0,0,0,16]," + "\"bar\":[100,0,0,0,0,0,0,0,0,1000]," + "\"col\":[\"Red Blue\",\"Green\",\"Red\"]," + "\"tests\":[{\"a\":0,\"b\":4},{\"a\":1,\"b\":2}]," + "\"text\":\"hello\"}}"); +#endif + + TEST_FLAGS(flatcc_json_parser_f_skip_array_overflow, 0, + "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0, 0, 0, 0, 0, 0," + " 0, 0, 0, 0, 0, 0, 0, 0, 16.0, 99]," + " \"bar\": [ 100, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 99]," + " \"text\":\"hello, world\"}}", + "{\"fixed_array\":{\"foo\":[1,2,0,0,0,0,0," + "0,0,0,0,0,0,0,0,16]," + "\"bar\":[100,0,0,0,0,0,0,0,0,1000]," + "\"col\":[0,0,0]," + "\"tests\":[{\"a\":0,\"b\":0},{\"a\":0,\"b\":0}]," + "\"text\":\"hello\"}}"); + + TEST( "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0 ]," + " \"bar\": [ 100 ], \"text\": \"K\\x00A\\x00\" }}", + "{\"fixed_array\":{\"foo\":[1,2,0,0,0,0,0," + "0,0,0,0,0,0,0,0,0]," + "\"bar\":[100,0,0,0,0,0,0,0,0,0]," + "\"col\":[0,0,0]," + "\"tests\":[{\"a\":0,\"b\":0},{\"a\":0,\"b\":0}]," + "\"text\":\"K\\u0000A\"}}"); + + TEST_ERROR_FLAGS(flatcc_json_parser_f_reject_array_underflow, 0, + "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0 ] }}", + flatcc_json_parser_error_array_underflow); + + TEST_ERROR_FLAGS(flatcc_json_parser_f_reject_array_underflow, 0, + "{ \"fixed_array\": { \"text\": \"K\\x00A\\x00\" }}", + flatcc_json_parser_error_array_underflow); + + TEST_ERROR( + "{ \"fixed_array\": { \"foo\": [ 1.0, 2.0, 0, 0, 0, 0, 0," + " 0, 0, 0, 0, 0, 0, 0, 0, 16.0, 99]," + " \"bar\": [ 100, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 99] }}", + flatcc_json_parser_error_array_overflow); + + END_TEST(); +} + +/* + * Here we cover some border cases around unions and flag + * enumerations, and nested buffers. + * + * More complex objects with struct members etc. are reasonably + * covered in the printer and parser tests using the golden data + * set. + */ +int main(void) +{ + BEGIN_TEST(Monster); + + ret |= edge_case_tests(); + ret |= error_case_tests(); + ret |= union_vector_tests(); + ret |= fixed_array_tests(); + ret |= base64_tests(); + ret |= mixed_type_union_tests(); + + /* Allow trailing comma. */ + TEST( "{ \"name\": \"Monster\", }", + "{\"name\":\"Monster\"}"); + + TEST( "{\"color\": \"Red\", \"name\": \"Monster\", }", + "{\"name\":\"Monster\",\"color\":\"Red\"}"); + + TEST( "{ \"name\": \"Monster\", \"color\": \"Green\" }", + "{\"name\":\"Monster\",\"color\":\"Green\"}"); + + TEST( "{ \"name\": \"Monster\", \"color\": \"Green Red Blue\" }", + "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}"); + + TEST( "{ \"name\": \"Monster\", \"color\": \" Green Red Blue \" }", + "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}"); + + TEST( "{ \"name\": \"Monster\", \"color\": \"Red\" }", + "{\"name\":\"Monster\",\"color\":\"Red\"}"); + + TEST( "{ \"name\": \"Monster\", \"color\" :\"Green\" }", + "{\"name\":\"Monster\",\"color\":\"Green\"}"); + + /* Default value. */ + TEST( "{ \"name\": \"Monster\", \"color\": \"Blue\" }", + "{\"name\":\"Monster\"}"); + + /* Default value. */ + TEST( "{ \"name\": \"Monster\", \"color\": 8}", + "{\"name\":\"Monster\"}"); +#if UQ + /* Allow trailing comma. */ + TEST( "{ name: \"Monster\", }", + "{\"name\":\"Monster\"}"); + + TEST( "{color: \"Red\", name: \"Monster\", }", + "{\"name\":\"Monster\",\"color\":\"Red\"}"); + + TEST( "{ name: \"Monster\", color: \"Green\" }", + "{\"name\":\"Monster\",\"color\":\"Green\"}"); + + TEST( "{ name: \"Monster\", color: \"Green Red Blue\" }", + "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}"); + + TEST( "{ name: \"Monster\", color: \" Green Red Blue \" }", + "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}"); + + TEST( "{ name: \"Monster\", color: Red }", + "{\"name\":\"Monster\",\"color\":\"Red\"}"); + + TEST( "{ name: \"Monster\", color: Green }", + "{\"name\":\"Monster\",\"color\":\"Green\"}"); + + /* Default value. */ + TEST( "{ name: \"Monster\", color: Blue }", + "{\"name\":\"Monster\"}"); + + /* Default value. */ + TEST( "{ name: \"Monster\", color: 8}", + "{\"name\":\"Monster\"}"); +#endif +#if UQL + TEST( "{ name: \"Monster\", color: Green Red }", + "{\"name\":\"Monster\",\"color\":\"Red Green\"}"); +#endif + +#if UQL + /* No leading space in unquoted flag. */ + TEST( "{ name: \"Monster\", color:Green Red }", + "{\"name\":\"Monster\",\"color\":\"Red Green\"}"); + + TEST( "{ name: \"Monster\", color: Green Red}", + "{\"name\":\"Monster\",\"color\":\"Red Green\"}"); + + TEST( "{ name: \"Monster\", color:Green Blue Red }", + "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}"); +#endif + + TEST( "{ \"name\": \"Monster\", \"color\": 1}", + "{\"name\":\"Monster\",\"color\":\"Red\"}"); + + TEST( "{ \"name\": \"Monster\", \"color\": 2}", + "{\"name\":\"Monster\",\"color\":\"Green\"}"); + + TEST( "{ \"name\": \"Monster\", \"color\": 9}", + "{\"name\":\"Monster\",\"color\":\"Red Blue\"}"); + + TEST( "{ \"name\": \"Monster\", \"color\": 11}", + "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}"); + + TEST( "{ \"name\": \"Monster\", \"color\": 12}", + "{\"name\":\"Monster\",\"color\":12}"); + + TEST( "{ \"name\": \"Monster\", \"color\": 15}", + "{\"name\":\"Monster\",\"color\":15}"); + + TEST( "{ \"name\": \"Monster\", \"color\": 0}", + "{\"name\":\"Monster\",\"color\":0}"); + + TEST( "{ \"name\": \"Monster\", \"color\": \"Color.Red\"}", + "{\"name\":\"Monster\",\"color\":\"Red\"}"); + + TEST( "{ \"name\": \"Monster\", \"color\": \"MyGame.Example.Color.Red\"}", + "{\"name\":\"Monster\",\"color\":\"Red\"}"); + + TEST( "{ \"name\": \"Monster\", \"hp\": \"Color.Green\"}", + "{\"hp\":2,\"name\":\"Monster\"}"); + + TEST( "{ \"name\": \"Monster\", \"hp\": \"Color.Green\"}", + "{\"hp\":2,\"name\":\"Monster\"}"); + + TEST( "{ \"name\": \"Monster\", \"test_type\": \"Monster\", \"test\": { \"name\": \"any Monster\" } }", + "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"any Monster\"}}"); + + /* This is tricky because the test field must be reparsed after discovering the test type. */ + TEST( "{ \"name\": \"Monster\", \"test\": { \"name\": \"second Monster\" }, \"test_type\": \"Monster\" }", + "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\"}}"); + + /* Also test that parsing can continue after reparse. */ + TEST( "{ \"name\": \"Monster\", \"test\": { \"name\": \"second Monster\" }, \"hp\":17, \"test_type\":\n \"Monster\", \"color\":\"Green\" }", + "{\"hp\":17,\"name\":\"Monster\",\"color\":\"Green\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\"}}"); + + /* Test that NONE is recognized, and that we do not get a missing table error.*/ + TEST( "{ \"name\": \"Monster\", \"test_type\": \"NONE\" }", + "{\"name\":\"Monster\"}"); + + TEST( "{ \"name\": \"Monster\", \"test_type\": 0 }", + "{\"name\":\"Monster\"}"); + +#if UQ + TEST( "{ name: \"Monster\", color: 1}", + "{\"name\":\"Monster\",\"color\":\"Red\"}"); + + TEST( "{ name: \"Monster\", color: 2}", + "{\"name\":\"Monster\",\"color\":\"Green\"}"); + + TEST( "{ name: \"Monster\", color: 9}", + "{\"name\":\"Monster\",\"color\":\"Red Blue\"}"); + + TEST( "{ name: \"Monster\", color: 11}", + "{\"name\":\"Monster\",\"color\":\"Red Green Blue\"}"); + + TEST( "{ name: \"Monster\", color: 12}", + "{\"name\":\"Monster\",\"color\":12}"); + + TEST( "{ name: \"Monster\", color: 15}", + "{\"name\":\"Monster\",\"color\":15}"); + + TEST( "{ name: \"Monster\", color: 0}", + "{\"name\":\"Monster\",\"color\":0}"); + + TEST( "{ name: \"Monster\", color: Color.Red}", + "{\"name\":\"Monster\",\"color\":\"Red\"}"); + + TEST( "{ name: \"Monster\", color: MyGame.Example.Color.Red}", + "{\"name\":\"Monster\",\"color\":\"Red\"}"); + + TEST( "{ name: \"Monster\", hp: Color.Green}", + "{\"hp\":2,\"name\":\"Monster\"}"); + + TEST( "{ name: \"Monster\", hp: Color.Green}", + "{\"hp\":2,\"name\":\"Monster\"}"); + + TEST( "{ name: \"Monster\", test_type: Monster, test: { name: \"any Monster\" } }", + "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"any Monster\"}}"); + + /* This is tricky because the test field must be reparsed after discovering the test type. */ + TEST( "{ name: \"Monster\", test: { name: \"second Monster\" }, test_type: Monster }", + "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\"}}"); + + /* Also test that parsing can continue after reparse. */ + TEST( "{ name: \"Monster\", test: { name: \"second Monster\" }, hp:17, test_type:\n Monster, color:Green }", + "{\"hp\":17,\"name\":\"Monster\",\"color\":\"Green\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\"}}"); + + /* Test that NONE is recognized, and that we do not get a missing table error.*/ + TEST( "{ name: \"Monster\", test_type: NONE }", + "{\"name\":\"Monster\"}"); + + TEST( "{ name: \"Monster\", test_type: 0 }", + "{\"name\":\"Monster\"}"); + +#endif + +#if UQL + /* + * Test that generic parsing handles multiple flags correctly during + * first pass before backtracking. + */ + TEST( "{ name: \"Monster\", test: { name: \"second Monster\", color: Red Green }, test_type: Monster }", + "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\",\"color\":\"Red Green\"}}"); +#endif + + /* Ditto quoted flags. */ + TEST( "{ \"name\": \"Monster\", \"test\": { \"name\": \"second Monster\", \"color\": \" Red Green \" }, \"test_type\": \"Monster\" }", + "{\"name\":\"Monster\",\"test_type\":\"Monster\",\"test\":{\"name\":\"second Monster\",\"color\":\"Red Green\"}}"); + + /* + * Note the '\/' becomes just '/', and that '/' also works in input. + * + * The json printer does not have a concept of \x it always uses + * unicode. + * + * We use json extension \x to inject a control 03 which extends + * to a printed unicode escape, while the \u00F8 is a valid + * character after encoding, and is thus not escaped after printing + * but rather becoems a two-byte utf-8 encoding of 'ΓΈ' which + * we use C encoding to form utf8 C3B8 == \u00F8. + */ + TEST( "{ \"name\": \"Mon\xfF\xFf\\x03s\\xC3\\xF9\\u00F8ter\\b\\f\\n\\r\\t\\\"\\\\\\/'/\", }", + "{\"name\":\"Mon\xff\xff\\u0003s\xc3\xf9\xc3\xb8ter\\b\\f\\n\\r\\t\\\"\\\\/'/\"}"); + + TEST( "{ \"name\": \"\\u168B\\u1691\"}", + "{\"name\":\"\xe1\x9a\x8b\xe1\x9a\x91\"}"); + + /* Nested flatbuffer, either is a known object, or as a vector. */ + TEST( "{ \"name\": \"Monster\", \"testnestedflatbuffer\":{ \"name\": \"sub Monster\" } }", + "{\"name\":\"Monster\",\"testnestedflatbuffer\":{\"name\":\"sub Monster\"}}"); + +#if FLATBUFFERS_PROTOCOL_IS_LE + TEST( "{ \"name\": \"Monster\", \"testnestedflatbuffer\":" + "[" /* start of nested flatbuffer, implicit size: 40 */ + "4,0,0,0," /* header: object offset = 4, no identifier */ + "248,255,255,255," /* vtable offset */ + "16,0,0,0," /* offset to name */ + "12,0,8,0,0,0,0,0,0,0,4,0," /* vtable */ + "11,0,0,0,115,117,98,32,77,111,110,115,116,101,114,0" /* name = "sub Monster" */ + "]" /* end of nested flatbuffer */ + "}", + "{\"name\":\"Monster\",\"testnestedflatbuffer\":{\"name\":\"sub Monster\"}}"); +#else + TEST( "{ \"name\": \"Monster\", \"testnestedflatbuffer\":" + "[" /* start of nested flatbuffer, implicit size: 40 */ + "0,0,0,4," /* header: object offset = 4, no identifier */ + "255,255,255,248," /* vtable offset */ + "0,0,0,16," /* offset to name */ + "0,12,0,8,0,0,0,0,0,0,0,4," /* vtable */ + "0,0,0,11,115,117,98,32,77,111,110,115,116,101,114,0" /* name = "sub Monster" */ + "]" /* end of nested flatbuffer */ + "}", + "{\"name\":\"Monster\",\"testnestedflatbuffer\":{\"name\":\"sub Monster\"}}"); +#endif + + /* Test empty table */ + TEST( "{ \"name\": \"Monster\", \"testempty\": {} }", + "{\"name\":\"Monster\",\"testempty\":{}}"); + + /* Test empty array */ + TEST( "{ \"name\": \"Monster\", \"testarrayoftables\": [] }", + "{\"name\":\"Monster\",\"testarrayoftables\":[]}"); + + /* Test JSON prefix parsing */ + TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{" + "\"testjsonprefixparsing\": { \"aaaa\": \"test\", \"aaaa12345\": 17 } }}}", + "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{" + "\"testjsonprefixparsing\":{\"aaaa\":\"test\",\"aaaa12345\":17}}}}"); + + /* TODO: this parses with the last to }} missing, although it does not add the broken objects. */ + TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{" + "\"testjsonprefixparsing\": { \"bbbb\": \"test\", \"bbbb1234\": 19 } }", + "{\"name\":\"Monster\"}"); + + TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{" + "\"testjsonprefixparsing\": { \"bbbb\": \"test\", \"bbbb1234\": 19 } }}}", + "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{" + "\"testjsonprefixparsing\":{\"bbbb\":\"test\",\"bbbb1234\":19}}}}"); + + TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{" + "\"testjsonprefixparsing\": { \"cccc\": \"test\", \"cccc1234\": 19, \"cccc12345\": 17 } }}}", + "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{" + "\"testjsonprefixparsing\":{\"cccc\":\"test\",\"cccc1234\":19,\"cccc12345\":17}}}}"); + + TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{" + "\"testjsonprefixparsing\": { \"dddd1234\": 19, \"dddd12345\": 17 } }}}", + "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{" + "\"testjsonprefixparsing\":{\"dddd1234\":19,\"dddd12345\":17}}}}"); + + TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{" + "\"testjsonprefixparsing2\": { \"aaaa_bbbb_steps\": 19, \"aaaa_bbbb_start_\": 17 } }}}", + "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{" + "\"testjsonprefixparsing2\":{\"aaaa_bbbb_steps\":19,\"aaaa_bbbb_start_\":17}}}}"); + + TEST( "{ \"name\": \"Monster\", \"test_type\":\"Alt\", \"test\":{\"prefix\":{" + "\"testjsonprefixparsing3\": { \"aaaa_bbbb_steps\": 19, \"aaaa_bbbb_start_steps\": 17 } }}}", + "{\"name\":\"Monster\",\"test_type\":\"Alt\",\"test\":{\"prefix\":{" + "\"testjsonprefixparsing3\":{\"aaaa_bbbb_steps\":19,\"aaaa_bbbb_start_steps\":17}}}}"); + + return ret ? -1: 0; +} diff --git a/test/json_test/test_json_parser.c b/test/json_test/test_json_parser.c new file mode 100644 index 0000000..a985cfa --- /dev/null +++ b/test/json_test/test_json_parser.c @@ -0,0 +1,164 @@ +#include <stdio.h> + +#ifndef FLATCC_BENCHMARK +#define FLATCC_BENCHMARK 0 +#endif + +/* Only needed for verification. */ +#include "monster_test_reader.h" +#include "monster_test_json_parser.h" +#include "flatcc/support/hexdump.h" +#include "flatcc/support/cdump.h" +#include "flatcc/support/readfile.h" + +#if FLATCC_BENCHMARK +#include "flatcc/support/elapsed.h" +#endif + +const char *filename = "monsterdata_test.golden"; + +#define BENCH_TITLE "monsterdata_test.golden" + +#ifdef NDEBUG +#define COMPILE_TYPE "(optimized)" +#else +#define COMPILE_TYPE "(debug)" +#endif + +#define FILE_SIZE_MAX (1024 * 10) + +#undef ns +#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Example, x) + +#define test_assert(x) do { if (!(x)) { assert(0); return -1; }} while(0) + +/* A helper to simplify creating buffers vectors from C-arrays. */ +#define c_vec_len(V) (sizeof(V)/sizeof((V)[0])) + +int verify_parse(void *buffer) +{ + ns(Test_struct_t) test; + ns(Vec3_struct_t) pos; + ns(Monster_table_t) monster = ns(Monster_as_root_with_identifier)(buffer, ns(Monster_file_identifier)); + + pos = ns(Monster_pos(monster)); + test_assert(pos); + test_assert(ns(Vec3_x(pos) == 1)); + test_assert(ns(Vec3_y(pos) == 2)); + test_assert(ns(Vec3_z(pos) == 3)); + test_assert(ns(Vec3_test1(pos) == 3.0)); + test_assert(ns(Vec3_test2(pos) == ns(Color_Green))); + test = ns(Vec3_test3(pos)); + test_assert(test); + test_assert(ns(Test_a(test)) == 5); + test_assert(ns(Test_b(test)) == 6); + + // TODO: hp and further fields + + return 0; + +} +// TODO: +// when running benchmark with the wrong size argument (output size +// instead of input size), the warmup loop iterates indefinitely in the +// first iteration. This suggests there is an end check missing somwhere +// and this needs to be debugged. The input size as of this writing is 701 +// bytes, and the output size is 288 bytes. +int test_parse(void) +{ +#if FLATCC_BENCHMARK + double t1, t2; + int i; + int rep = 1000000; + int warmup_rep = 1000000; +#endif + + const char *buf; + void *flatbuffer = 0; + size_t in_size, out_size; + flatcc_json_parser_t ctx; + flatcc_builder_t builder; + flatcc_builder_t *B = &builder; + int ret = -1; + flatcc_json_parser_flags_t flags = 0; + + flatcc_builder_init(B); + + buf = readfile(filename, FILE_SIZE_MAX, &in_size); + if (!buf) { + fprintf(stderr, "%s: could not read input json file\n", filename); + return -1; + } + + if (monster_test_parse_json(B, &ctx, buf, in_size, flags)) { + goto failed; + } + fprintf(stderr, "%s: successfully parsed %d lines\n", filename, ctx.line); + flatbuffer = flatcc_builder_finalize_aligned_buffer(B, &out_size); + hexdump("parsed monsterdata_test.golden", flatbuffer, out_size, stdout); + fprintf(stderr, "input size: %lu, output size: %lu\n", + (unsigned long)in_size, (unsigned long)out_size); + verify_parse(flatbuffer); + + cdump("golden", flatbuffer, out_size, stdout); + + flatcc_builder_reset(B); +#if FLATCC_BENCHMARK + fprintf(stderr, "Now warming up\n"); + for (i = 0; i < warmup_rep; ++i) { + if (monster_test_parse_json(B, &ctx, buf, in_size, flags)) { + goto failed; + } + flatcc_builder_reset(B); + } + + fprintf(stderr, "Now benchmarking\n"); + t1 = elapsed_realtime(); + for (i = 0; i < rep; ++i) { + if (monster_test_parse_json(B, &ctx, buf, in_size, flags)) { + goto failed; + } + flatcc_builder_reset(B); + } + t2 = elapsed_realtime(); + + printf("----\n"); + show_benchmark(BENCH_TITLE " C generated JSON parse " COMPILE_TYPE, t1, t2, in_size, rep, "1M"); +#endif + ret = 0; + +done: + if (flatbuffer) { + flatcc_builder_aligned_free(flatbuffer); + } + if (buf) { + free((void *)buf); + } + flatcc_builder_clear(B); + return ret; + +failed: + fprintf(stderr, "%s:%d:%d: %s\n", + filename, (int)ctx.line, (int)(ctx.error_loc - ctx.line_start + 1), + flatcc_json_parser_error_string(ctx.error)); + goto done; +} + +/* We take arguments so test can run without copying sources. */ +#define usage \ +"wrong number of arguments:\n" \ +"usage: <program> [<input-filename>]\n" + +int main(int argc, const char *argv[]) +{ + fprintf(stderr, "JSON parse test\n"); + + if (argc != 1 && argc != 2) { + fprintf(stderr, usage); + exit(1); + } + if (argc == 2) { + filename = argv[1]; + } + return test_parse(); +} diff --git a/test/json_test/test_json_printer.c b/test/json_test/test_json_printer.c new file mode 100644 index 0000000..efbd572 --- /dev/null +++ b/test/json_test/test_json_printer.c @@ -0,0 +1,129 @@ +#include <stdio.h> + +/* Only needed for verification. */ +#include "monster_test_json_printer.h" +#include "flatcc/support/readfile.h" +#include "flatcc_golden.c" + +#ifdef NDEBUG +#define COMPILE_TYPE "(optimized)" +#else +#define COMPILE_TYPE "(debug)" +#endif + +#define FILE_SIZE_MAX (1024 * 10) + +#undef ns +#define ns(x) FLATBUFFERS_WRAP_NAMESPACE(MyGame_Example, x) + +/* A helper to simplify creating buffers vectors from C-arrays. */ +#define c_vec_len(V) (sizeof(V)/sizeof((V)[0])) + +const char *filename = 0; /* "monsterdata_test.mon"; */ +const char *golden_filename = "monsterdata_test.golden"; +const char *target_filename = "monsterdata_test.json.txt"; + +int test_print(void) +{ + int ret = 0; + const char *buf = 0; + const char *golden = 0; + const char *target = 0; + size_t size = 0, golden_size = 0, target_size = 0; + flatcc_json_printer_t ctx_obj, *ctx; + FILE *fp = 0; + + ctx = &ctx_obj; + + fp = fopen(target_filename, "wb"); + if (!fp) { + fprintf(stderr, "%s: could not open output file\n", target_filename); + /* ctx not ready for clenaup, so exit directly. */ + return -1; + } + flatcc_json_printer_init(ctx, fp); + /* Uses same formatting as golden reference file. */ + flatcc_json_printer_set_nonstrict(ctx); + + if (filename && strcmp(filename, "-")) { + buf = readfile(filename, FILE_SIZE_MAX, &size); + } else { +#if FLATBUFFERS_PROTOCOL_IS_LE + buf = (const char *)flatcc_golden_le; + size = sizeof(flatcc_golden_le); +#else + buf = (const char *)flatcc_golden_be; + size = sizeof(flatcc_golden_be); +#endif + } + + if (!buf) { + fprintf(stderr, "%s: could not read input flatbuffer file\n", filename); + goto fail; + } + golden = readfile(golden_filename, FILE_SIZE_MAX, &golden_size); + if (!golden) { + fprintf(stderr, "%s: could not read verification json file\n", golden_filename); + goto fail; + } + ns(Monster_print_json_as_root(ctx, buf, size, "MONS")); + flatcc_json_printer_flush(ctx); + if (flatcc_json_printer_get_error(ctx)) { + printf("could not print monster data\n"); + } + fclose(fp); + fp = 0; + target = readfile(target_filename, FILE_SIZE_MAX, &target_size); + if (!target) { + fprintf(stderr, "%s: could not read back output file\n", target_filename); + goto fail; + } + if (target_size != golden_size || memcmp(target, golden, target_size)) { + fprintf(stderr, "generated output file did not match verification file\n"); + goto fail; + } + fprintf(stderr, "json print test succeeded\n"); + +done: + flatcc_json_printer_clear(ctx); + if (!filename) { + buf = 0; + } + if (buf) { + free((void *)buf); + } + if (golden) { + free((void *)golden); + } + if (target) { + free((void *)target); + } + if (fp) { + fclose(fp); + } + return ret; +fail: + ret = -1; + goto done; +} + +/* We take arguments so output file can be generated in build directory without copying sources. */ +#define usage \ +"wrong number of arguments:\n" \ +"usage: <program> [(<input-filename>|'-') <reference-filename> <output-filename>]\n" \ +" noargs, or '-' use default binary buffer matching endianness of flatbuffer format\n" + +int main(int argc, const char *argv[]) +{ + fprintf(stderr, "running json print test\n"); + if (argc != 1 && argc != 4) { + fprintf(stderr, usage); + exit(1); + } + if (argc == 4) { + filename = argv[1]; + golden_filename = argv[2]; + target_filename = argv[3]; + } + return test_print(); +} |