/* * * A set of tests for JSON parsing and serialization. */ #include "tests/test_util.h" #include "tests/upb_test.h" #include "upb/handlers.h" #include "upb/symtab.h" #include "upb/json/printer.h" #include "upb/json/parser.h" #include "upb/upb.h" #include // 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 void AddField(upb::MessageDef* message, int number, const char* name, upb_fieldtype_t type, bool is_repeated, const upb::Def* subdef = NULL) { upb::reffed_ptr field(upb::FieldDef::New()); upb::Status st; field->set_name(name, &st); field->set_type(type); field->set_label(is_repeated ? UPB_LABEL_REPEATED : UPB_LABEL_OPTIONAL); field->set_number(number, &st); if (subdef) { field->set_subdef(subdef, &st); } message->AddField(field, &st); } static const upb::MessageDef* BuildTestMessage( upb::reffed_ptr symtab) { upb::Status st; // Create SubMessage. upb::reffed_ptr submsg(upb::MessageDef::New()); submsg->set_full_name("SubMessage", &st); AddField(submsg.get(), 1, "foo", UPB_TYPE_INT32, false); // Create MapEntryStringString. upb::reffed_ptr mapentry_string_string( upb::MessageDef::New()); mapentry_string_string->set_full_name("MapEntry_String_String", &st); mapentry_string_string->setmapentry(true); AddField(mapentry_string_string.get(), 1, "key", UPB_TYPE_STRING, false); AddField(mapentry_string_string.get(), 2, "value", UPB_TYPE_STRING, false); // Create MapEntryInt32String. upb::reffed_ptr mapentry_int32_string( upb::MessageDef::New()); mapentry_int32_string->set_full_name("MapEntry_Int32_String", &st); mapentry_int32_string->setmapentry(true); AddField(mapentry_int32_string.get(), 1, "key", UPB_TYPE_INT32, false); AddField(mapentry_int32_string.get(), 2, "value", UPB_TYPE_STRING, false); // Create MapEntryBoolString. upb::reffed_ptr mapentry_bool_string( upb::MessageDef::New()); mapentry_bool_string->set_full_name("MapEntry_Bool_String", &st); mapentry_bool_string->setmapentry(true); AddField(mapentry_bool_string.get(), 1, "key", UPB_TYPE_BOOL, false); AddField(mapentry_bool_string.get(), 2, "value", UPB_TYPE_STRING, false); // Create MapEntryStringInt32. upb::reffed_ptr mapentry_string_int32( upb::MessageDef::New()); mapentry_string_int32->set_full_name("MapEntry_String_Int32", &st); mapentry_string_int32->setmapentry(true); AddField(mapentry_string_int32.get(), 1, "key", UPB_TYPE_STRING, false); AddField(mapentry_string_int32.get(), 2, "value", UPB_TYPE_INT32, false); // Create MapEntryStringBool. upb::reffed_ptr mapentry_string_bool(upb::MessageDef::New()); mapentry_string_bool->set_full_name("MapEntry_String_Bool", &st); mapentry_string_bool->setmapentry(true); AddField(mapentry_string_bool.get(), 1, "key", UPB_TYPE_STRING, false); AddField(mapentry_string_bool.get(), 2, "value", UPB_TYPE_BOOL, false); // Create MapEntryStringMessage. upb::reffed_ptr mapentry_string_msg(upb::MessageDef::New()); mapentry_string_msg->set_full_name("MapEntry_String_Message", &st); mapentry_string_msg->setmapentry(true); AddField(mapentry_string_msg.get(), 1, "key", UPB_TYPE_STRING, false); AddField(mapentry_string_msg.get(), 2, "value", UPB_TYPE_MESSAGE, false, upb::upcast(submsg.get())); // Create MyEnum. upb::reffed_ptr myenum(upb::EnumDef::New()); myenum->set_full_name("MyEnum", &st); myenum->AddValue("A", 0, &st); myenum->AddValue("B", 1, &st); myenum->AddValue("C", 2, &st); // Create TestMessage. upb::reffed_ptr md(upb::MessageDef::New()); md->set_full_name("TestMessage", &st); AddField(md.get(), 1, "optional_int32", UPB_TYPE_INT32, false); AddField(md.get(), 2, "optional_int64", UPB_TYPE_INT64, false); AddField(md.get(), 3, "optional_uint32", UPB_TYPE_UINT32, false); AddField(md.get(), 4, "optional_uint64", UPB_TYPE_UINT64, false); AddField(md.get(), 5, "optional_string", UPB_TYPE_STRING, false); AddField(md.get(), 6, "optional_bytes", UPB_TYPE_BYTES, false); AddField(md.get(), 7, "optional_bool" , UPB_TYPE_BOOL, false); AddField(md.get(), 8, "optional_msg" , UPB_TYPE_MESSAGE, false, upb::upcast(submsg.get())); AddField(md.get(), 9, "optional_enum", UPB_TYPE_ENUM, false, upb::upcast(myenum.get())); AddField(md.get(), 11, "repeated_int32", UPB_TYPE_INT32, true); AddField(md.get(), 12, "repeated_int64", UPB_TYPE_INT64, true); AddField(md.get(), 13, "repeated_uint32", UPB_TYPE_UINT32, true); AddField(md.get(), 14, "repeated_uint64", UPB_TYPE_UINT64, true); AddField(md.get(), 15, "repeated_string", UPB_TYPE_STRING, true); AddField(md.get(), 16, "repeated_bytes", UPB_TYPE_BYTES, true); AddField(md.get(), 17, "repeated_bool" , UPB_TYPE_BOOL, true); AddField(md.get(), 18, "repeated_msg" , UPB_TYPE_MESSAGE, true, upb::upcast(submsg.get())); AddField(md.get(), 19, "optional_enum", UPB_TYPE_ENUM, true, upb::upcast(myenum.get())); AddField(md.get(), 20, "map_string_string", UPB_TYPE_MESSAGE, true, upb::upcast(mapentry_string_string.get())); AddField(md.get(), 21, "map_int32_string", UPB_TYPE_MESSAGE, true, upb::upcast(mapentry_int32_string.get())); AddField(md.get(), 22, "map_bool_string", UPB_TYPE_MESSAGE, true, upb::upcast(mapentry_bool_string.get())); AddField(md.get(), 23, "map_string_int32", UPB_TYPE_MESSAGE, true, upb::upcast(mapentry_string_int32.get())); AddField(md.get(), 24, "map_string_bool", UPB_TYPE_MESSAGE, true, upb::upcast(mapentry_string_bool.get())); AddField(md.get(), 25, "map_string_msg", UPB_TYPE_MESSAGE, true, upb::upcast(mapentry_string_msg.get())); // Add both to our symtab. upb::Def* defs[9] = { upb::upcast(submsg.ReleaseTo(&defs)), upb::upcast(myenum.ReleaseTo(&defs)), upb::upcast(md.ReleaseTo(&defs)), upb::upcast(mapentry_string_string.ReleaseTo(&defs)), upb::upcast(mapentry_int32_string.ReleaseTo(&defs)), upb::upcast(mapentry_bool_string.ReleaseTo(&defs)), upb::upcast(mapentry_string_int32.ReleaseTo(&defs)), upb::upcast(mapentry_string_bool.ReleaseTo(&defs)), upb::upcast(mapentry_string_msg.ReleaseTo(&defs)), }; symtab->Add(defs, 9, &defs, &st); ASSERT(st.ok()); // Return TestMessage. return symtab->LookupMessage("TestMessage"); } 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(_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::ParserMethod* parser_method, int seam) { VerboseParserEnvironment env(verbose); StringSink data_sink; upb::json::Printer* printer = upb::json::Printer::Create( env.env(), serialize_handlers, data_sink.Sink()); upb::json::Parser* parser = upb::json::Parser::Create(env.env(), parser_method, printer->input()); 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" "Original:\n%s\nParsed/Serialized:\n%s\n", json_src, 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::reffed_ptr symtab(upb::SymbolTable::New()); const upb::MessageDef* md = BuildTestMessage(symtab.get()); upb::reffed_ptr serialize_handlers( upb::json::Printer::NewHandlers(md, false)); upb::reffed_ptr parser_method( upb::json::ParserMethod::New(md)); 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.get(), parser_method.get(), i); } } serialize_handlers = upb::json::Printer::NewHandlers(md, true); 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.get(), parser_method.get(), i); } } } extern "C" { int run_tests(int argc, char *argv[]) { UPB_UNUSED(argc); UPB_UNUSED(argv); test_json_roundtrip(); return 0; } }