| /* |
| * |
| * A set of tests for JSON parsing and serialization. |
| */ |
| |
| #include <string> |
| |
| #include "tests/json/test.upb.h" // Test that it compiles for C++. |
| #include "tests/json/test.upbdefs.h" |
| #include "tests/test_util.h" |
| #include "tests/upb_test.h" |
| #include "upb/def.hpp" |
| #include "upb/handlers.h" |
| #include "upb/json/parser.h" |
| #include "upb/json/printer.h" |
| #include "upb/port_def.inc" |
| #include "upb/upb.h" |
| |
| // Macros for readability in test case list: allows us to give TEST("...") / |
| // EXPECT("...") pairs. |
| #define TEST(x) x |
| #define EXPECT_SAME NULL |
| #define EXPECT(x) x |
| #define TEST_SENTINEL { NULL, NULL } |
| |
| struct TestCase { |
| const char* input; |
| const char* expected; |
| }; |
| |
| bool verbose = false; |
| |
| static TestCase kTestRoundtripMessages[] = { |
| // Test most fields here. |
| { |
| TEST("{\"optionalInt32\":-42,\"optionalString\":\"Test\\u0001Message\"," |
| "\"optionalMsg\":{\"foo\":42}," |
| "\"optionalBool\":true,\"repeatedMsg\":[{\"foo\":1}," |
| "{\"foo\":2}]}"), |
| EXPECT_SAME |
| }, |
| // We must also recognize raw proto names. |
| { |
| TEST("{\"optional_int32\":-42,\"optional_string\":\"Test\\u0001Message\"," |
| "\"optional_msg\":{\"foo\":42}," |
| "\"optional_bool\":true,\"repeated_msg\":[{\"foo\":1}," |
| "{\"foo\":2}]}"), |
| EXPECT("{\"optionalInt32\":-42,\"optionalString\":\"Test\\u0001Message\"," |
| "\"optionalMsg\":{\"foo\":42}," |
| "\"optionalBool\":true,\"repeatedMsg\":[{\"foo\":1}," |
| "{\"foo\":2}]}") |
| }, |
| // Test special escapes in strings. |
| { |
| TEST("{\"repeatedString\":[\"\\b\",\"\\r\",\"\\n\",\"\\f\",\"\\t\"," |
| "\"\uFFFF\"]}"), |
| EXPECT_SAME |
| }, |
| // Test enum symbolic names. |
| { |
| // The common case: parse and print the symbolic name. |
| TEST("{\"optionalEnum\":\"A\"}"), |
| EXPECT_SAME |
| }, |
| { |
| // Unknown enum value: will be printed as an integer. |
| TEST("{\"optionalEnum\":42}"), |
| EXPECT_SAME |
| }, |
| { |
| // Known enum value: we're happy to parse an integer but we will re-emit the |
| // symbolic name. |
| TEST("{\"optionalEnum\":1}"), |
| EXPECT("{\"optionalEnum\":\"B\"}") |
| }, |
| // UTF-8 tests: escapes -> literal UTF8 in output. |
| { |
| // Note double escape on \uXXXX: we want the escape to be processed by the |
| // JSON parser, not by the C++ compiler! |
| TEST("{\"optionalString\":\"\\u007F\"}"), |
| EXPECT("{\"optionalString\":\"\x7F\"}") |
| }, |
| { |
| TEST("{\"optionalString\":\"\\u0080\"}"), |
| EXPECT("{\"optionalString\":\"\xC2\x80\"}") |
| }, |
| { |
| TEST("{\"optionalString\":\"\\u07FF\"}"), |
| EXPECT("{\"optionalString\":\"\xDF\xBF\"}") |
| }, |
| { |
| TEST("{\"optionalString\":\"\\u0800\"}"), |
| EXPECT("{\"optionalString\":\"\xE0\xA0\x80\"}") |
| }, |
| { |
| TEST("{\"optionalString\":\"\\uFFFF\"}"), |
| EXPECT("{\"optionalString\":\"\xEF\xBF\xBF\"}") |
| }, |
| // map-field tests |
| { |
| TEST("{\"mapStringString\":{\"a\":\"value1\",\"b\":\"value2\"," |
| "\"c\":\"value3\"}}"), |
| EXPECT_SAME |
| }, |
| { |
| TEST("{\"mapInt32String\":{\"1\":\"value1\",\"-1\":\"value2\"," |
| "\"1234\":\"value3\"}}"), |
| EXPECT_SAME |
| }, |
| { |
| TEST("{\"mapBoolString\":{\"false\":\"value1\",\"true\":\"value2\"}}"), |
| EXPECT_SAME |
| }, |
| { |
| TEST("{\"mapStringInt32\":{\"asdf\":1234,\"jkl;\":-1}}"), |
| EXPECT_SAME |
| }, |
| { |
| TEST("{\"mapStringBool\":{\"asdf\":true,\"jkl;\":false}}"), |
| EXPECT_SAME |
| }, |
| { |
| TEST("{\"mapStringMsg\":{\"asdf\":{\"foo\":42},\"jkl;\":{\"foo\":84}}}"), |
| EXPECT_SAME |
| }, |
| TEST_SENTINEL |
| }; |
| |
| static TestCase kTestRoundtripMessagesPreserve[] = { |
| // Test most fields here. |
| { |
| TEST("{\"optional_int32\":-42,\"optional_string\":\"Test\\u0001Message\"," |
| "\"optional_msg\":{\"foo\":42}," |
| "\"optional_bool\":true,\"repeated_msg\":[{\"foo\":1}," |
| "{\"foo\":2}]}"), |
| EXPECT_SAME |
| }, |
| TEST_SENTINEL |
| }; |
| |
| static TestCase kTestSkipUnknown[] = { |
| { |
| TEST("{\"optionalEnum\":\"UNKNOWN_ENUM_VALUE\"}"), |
| EXPECT("{}"), |
| }, |
| TEST_SENTINEL |
| }; |
| |
| static TestCase kTestFailure[] = { |
| { |
| TEST("{\"optionalEnum\":\"UNKNOWN_ENUM_VALUE\"}"), |
| EXPECT("{}"), /* Actually we expect error, this is checked later. */ |
| }, |
| TEST_SENTINEL |
| }; |
| |
| class StringSink { |
| public: |
| StringSink() { |
| upb_byteshandler_init(&byteshandler_); |
| upb_byteshandler_setstring(&byteshandler_, &str_handler, NULL); |
| upb_bytessink_reset(&bytessink_, &byteshandler_, &s_); |
| } |
| ~StringSink() { } |
| |
| upb_bytessink Sink() { return bytessink_; } |
| |
| const std::string& Data() { return s_; } |
| |
| private: |
| |
| static size_t str_handler(void* _closure, const void* hd, |
| const char* data, size_t len, |
| const upb_bufhandle* handle) { |
| UPB_UNUSED(hd); |
| UPB_UNUSED(handle); |
| std::string* s = static_cast<std::string*>(_closure); |
| std::string appended(data, len); |
| s->append(data, len); |
| return len; |
| } |
| |
| upb_byteshandler byteshandler_; |
| upb_bytessink bytessink_; |
| std::string s_; |
| }; |
| |
| void test_json_roundtrip_message(const char* json_src, |
| const char* json_expected, |
| const upb::Handlers* serialize_handlers, |
| const upb::json::ParserMethodPtr parser_method, |
| int seam, |
| bool ignore_unknown) { |
| VerboseParserEnvironment env(verbose); |
| StringSink data_sink; |
| upb::json::PrinterPtr printer = upb::json::PrinterPtr::Create( |
| env.arena(), serialize_handlers, data_sink.Sink()); |
| upb::json::ParserPtr parser = upb::json::ParserPtr::Create( |
| env.arena(), parser_method, NULL, printer.input(), |
| env.status(), ignore_unknown); |
| env.ResetBytesSink(parser.input()); |
| env.Reset(json_src, strlen(json_src), false, false); |
| |
| bool ok = env.Start() && |
| env.ParseBuffer(seam) && |
| env.ParseBuffer(-1) && |
| env.End(); |
| |
| ASSERT(ok); |
| ASSERT(env.CheckConsistency()); |
| |
| if (memcmp(json_expected, |
| data_sink.Data().data(), |
| data_sink.Data().size())) { |
| fprintf(stderr, |
| "JSON parse/serialize roundtrip result differs:\n" |
| "Expected:\n%s\nParsed/Serialized:\n%s\n", |
| json_expected, data_sink.Data().c_str()); |
| abort(); |
| } |
| } |
| |
| // Starts with a message in JSON format, parses and directly serializes again, |
| // and compares the result. |
| void test_json_roundtrip() { |
| upb::SymbolTable symtab; |
| upb::HandlerCache serialize_handlercache( |
| upb::json::PrinterPtr::NewCache(false)); |
| upb::json::CodeCache parse_codecache; |
| |
| upb::MessageDefPtr md(upb_test_json_TestMessage_getmsgdef(symtab.ptr())); |
| ASSERT(md); |
| const upb::Handlers* serialize_handlers = serialize_handlercache.Get(md); |
| const upb::json::ParserMethodPtr parser_method = parse_codecache.Get(md); |
| ASSERT(serialize_handlers); |
| |
| for (const TestCase* test_case = kTestRoundtripMessages; |
| test_case->input != NULL; test_case++) { |
| const char *expected = |
| (test_case->expected == EXPECT_SAME) ? |
| test_case->input : |
| test_case->expected; |
| |
| for (size_t i = 0; i < strlen(test_case->input); i++) { |
| test_json_roundtrip_message(test_case->input, expected, |
| serialize_handlers, parser_method, (int)i, |
| false); |
| } |
| } |
| |
| // Tests ignore unknown. |
| for (const TestCase* test_case = kTestSkipUnknown; |
| test_case->input != NULL; test_case++) { |
| const char *expected = |
| (test_case->expected == EXPECT_SAME) ? |
| test_case->input : |
| test_case->expected; |
| |
| for (size_t i = 0; i < strlen(test_case->input); i++) { |
| test_json_roundtrip_message(test_case->input, expected, |
| serialize_handlers, parser_method, (int)i, |
| true); |
| } |
| } |
| |
| serialize_handlercache = upb::json::PrinterPtr::NewCache(true); |
| serialize_handlers = serialize_handlercache.Get(md); |
| |
| for (const TestCase* test_case = kTestRoundtripMessagesPreserve; |
| test_case->input != NULL; test_case++) { |
| const char *expected = |
| (test_case->expected == EXPECT_SAME) ? |
| test_case->input : |
| test_case->expected; |
| |
| for (size_t i = 0; i < strlen(test_case->input); i++) { |
| test_json_roundtrip_message(test_case->input, expected, |
| serialize_handlers, parser_method, (int)i, |
| false); |
| } |
| } |
| } |
| |
| void test_json_parse_failure(const char* json_src, |
| const upb::Handlers* serialize_handlers, |
| const upb::json::ParserMethodPtr parser_method, |
| int seam) { |
| VerboseParserEnvironment env(verbose); |
| StringSink data_sink; |
| upb::json::PrinterPtr printer = upb::json::PrinterPtr::Create( |
| env.arena(), serialize_handlers, data_sink.Sink()); |
| upb::json::ParserPtr parser = upb::json::ParserPtr::Create( |
| env.arena(), parser_method, NULL, printer.input(), env.status(), false); |
| env.ResetBytesSink(parser.input()); |
| env.Reset(json_src, strlen(json_src), false, true); |
| |
| bool ok = env.Start() && |
| env.ParseBuffer(seam) && |
| env.ParseBuffer(-1) && |
| env.End(); |
| |
| ASSERT(!ok); |
| ASSERT(env.CheckConsistency()); |
| } |
| |
| // Starts with a proto message in JSON format, parses and expects failre. |
| void test_json_failure() { |
| upb::SymbolTable symtab; |
| upb::HandlerCache serialize_handlercache( |
| upb::json::PrinterPtr::NewCache(false)); |
| upb::json::CodeCache parse_codecache; |
| |
| upb::MessageDefPtr md(upb_test_json_TestMessage_getmsgdef(symtab.ptr())); |
| ASSERT(md); |
| const upb::Handlers* serialize_handlers = serialize_handlercache.Get(md); |
| const upb::json::ParserMethodPtr parser_method = parse_codecache.Get(md); |
| ASSERT(serialize_handlers); |
| |
| for (const TestCase* test_case = kTestFailure; |
| test_case->input != NULL; test_case++) { |
| for (size_t i = 0; i < strlen(test_case->input); i++) { |
| test_json_parse_failure(test_case->input, serialize_handlers, |
| parser_method, (int)i); |
| } |
| } |
| } |
| |
| extern "C" { |
| int run_tests(int argc, char *argv[]) { |
| UPB_UNUSED(argc); |
| UPB_UNUSED(argv); |
| test_json_roundtrip(); |
| test_json_failure(); |
| return 0; |
| } |
| } |